| /* |
| * Copyright (C) 2008 Esmertec AG. |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.mms.ui; |
| |
| import static android.content.res.Configuration.KEYBOARDHIDDEN_NO; |
| import static com.android.mms.transaction.ProgressCallbackEntity.PROGRESS_ABORT; |
| import static com.android.mms.transaction.ProgressCallbackEntity.PROGRESS_COMPLETE; |
| import static com.android.mms.transaction.ProgressCallbackEntity.PROGRESS_START; |
| import static com.android.mms.transaction.ProgressCallbackEntity.PROGRESS_STATUS_ACTION; |
| import static com.android.mms.ui.MessageListAdapter.COLUMN_ID; |
| import static com.android.mms.ui.MessageListAdapter.COLUMN_MSG_TYPE; |
| import static com.android.mms.ui.MessageListAdapter.PROJECTION; |
| |
| import com.android.internal.telephony.CallerInfo; |
| import com.android.mms.ExceedMessageSizeException; |
| import com.android.mms.R; |
| import com.android.mms.ResolutionException; |
| import com.android.mms.UnsupportContentTypeException; |
| import com.android.mms.model.SlideModel; |
| import com.android.mms.model.SlideshowModel; |
| import com.android.mms.model.TextModel; |
| import com.android.mms.transaction.MessageSender; |
| import com.android.mms.transaction.MessagingNotification; |
| import com.android.mms.transaction.MmsMessageSender; |
| import com.android.mms.transaction.SmsMessageSender; |
| import com.android.mms.ui.AttachmentEditor.OnAttachmentChangedListener; |
| import com.android.mms.ui.MessageUtils.ResizeImageResultCallback; |
| import com.android.mms.ui.RecipientList.Recipient; |
| import com.android.mms.ui.RecipientsEditor.RecipientContextMenuInfo; |
| import com.android.mms.util.ContactInfoCache; |
| import com.android.mms.util.SendingProgressTokenManager; |
| import com.android.mms.util.SmileyParser; |
| |
| import com.google.android.mms.ContentType; |
| import com.google.android.mms.MmsException; |
| import com.google.android.mms.pdu.EncodedStringValue; |
| import com.google.android.mms.pdu.PduBody; |
| import com.google.android.mms.pdu.PduHeaders; |
| import com.google.android.mms.pdu.PduPart; |
| import com.google.android.mms.pdu.PduPersister; |
| import com.google.android.mms.pdu.SendReq; |
| import com.google.android.mms.util.SqliteWrapper; |
| |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.content.AsyncQueryHandler; |
| import android.content.BroadcastReceiver; |
| import android.content.ContentResolver; |
| import android.content.ContentUris; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.DialogInterface.OnClickListener; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| import android.database.Cursor; |
| import android.database.DatabaseUtils; |
| import android.database.sqlite.SQLiteException; |
| import android.graphics.Bitmap; |
| import android.graphics.drawable.Drawable; |
| import android.media.RingtoneManager; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.provider.Contacts; |
| import android.provider.MediaStore; |
| import android.provider.Settings; |
| import android.provider.Contacts.People; |
| import android.provider.Contacts.Intents.Insert; |
| import android.provider.Telephony.Mms; |
| import android.provider.Telephony.Sms; |
| import android.provider.Telephony.Threads; |
| import android.telephony.gsm.SmsMessage; |
| import android.text.ClipboardManager; |
| import android.text.Editable; |
| import android.text.InputFilter; |
| import android.text.SpannableString; |
| import android.text.Spanned; |
| import android.text.TextUtils; |
| import android.text.TextWatcher; |
| import android.text.method.TextKeyListener; |
| import android.text.style.URLSpan; |
| import android.text.util.Linkify; |
| import android.util.Config; |
| import android.util.Log; |
| import android.view.ContextMenu; |
| import android.view.KeyEvent; |
| import android.view.LayoutInflater; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.view.View; |
| import android.view.ViewStub; |
| import android.view.Window; |
| import android.view.ContextMenu.ContextMenuInfo; |
| import android.view.View.OnCreateContextMenuListener; |
| import android.view.View.OnFocusChangeListener; |
| import android.view.View.OnKeyListener; |
| import android.view.inputmethod.InputMethodManager; |
| import android.widget.AdapterView; |
| import android.widget.Button; |
| import android.widget.EditText; |
| import android.widget.ImageView; |
| import android.widget.LinearLayout; |
| import android.widget.ListView; |
| import android.widget.SimpleAdapter; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| |
| import java.io.InputStream; |
| import java.io.IOException; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import android.webkit.MimeTypeMap; |
| |
| /** |
| * This is the main UI for: |
| * 1. Composing a new message; |
| * 2. Viewing/managing message history of a conversation. |
| * |
| * This activity can handle following parameters from the intent |
| * by which it's launched. |
| * thread_id long Identify the conversation to be viewed. When creating a |
| * new message, this parameter shouldn't be present. |
| * msg_uri Uri The message which should be opened for editing in the editor. |
| * address String The addresses of the recipients in current conversation. |
| * compose_mode boolean Setting compose_mode to true will force the activity |
| * to show the recipients editor and the attachment editor but hide |
| * the message history. By default, this flag is set to false. |
| * exit_on_sent boolean Exit this activity after the message is sent. |
| */ |
| public class ComposeMessageActivity extends Activity |
| implements View.OnClickListener, OnAttachmentChangedListener { |
| public static final int REQUEST_CODE_ATTACH_IMAGE = 10; |
| public static final int REQUEST_CODE_TAKE_PICTURE = 11; |
| public static final int REQUEST_CODE_ATTACH_VIDEO = 12; |
| public static final int REQUEST_CODE_TAKE_VIDEO = 13; |
| public static final int REQUEST_CODE_ATTACH_SOUND = 14; |
| public static final int REQUEST_CODE_RECORD_SOUND = 15; |
| public static final int REQUEST_CODE_CREATE_SLIDESHOW = 16; |
| |
| private static final String TAG = "ComposeMessageActivity"; |
| private static final boolean DEBUG = false; |
| private static final boolean TRACE = false; |
| private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV; |
| |
| // Menu ID |
| private static final int MENU_ADD_SUBJECT = 0; |
| private static final int MENU_DELETE_THREAD = 1; |
| private static final int MENU_ADD_ATTACHMENT = 2; |
| private static final int MENU_DISCARD = 3; |
| private static final int MENU_SEND = 4; |
| private static final int MENU_CALL_RECIPIENT = 5; |
| private static final int MENU_CONVERSATION_LIST = 6; |
| |
| // Context menu ID |
| private static final int MENU_VIEW_CONTACT = 12; |
| private static final int MENU_ADD_TO_CONTACTS = 13; |
| |
| private static final int MENU_EDIT_MESSAGE = 14; |
| private static final int MENU_VIEW_PICTURE = 15; |
| private static final int MENU_VIEW_SLIDESHOW = 16; |
| private static final int MENU_VIEW_MESSAGE_DETAILS = 17; |
| private static final int MENU_DELETE_MESSAGE = 18; |
| private static final int MENU_SEARCH = 19; |
| private static final int MENU_DELIVERY_REPORT = 20; |
| private static final int MENU_FORWARD_MESSAGE = 21; |
| private static final int MENU_CALL_BACK = 22; |
| private static final int MENU_SEND_EMAIL = 23; |
| private static final int MENU_COPY_MESSAGE_TEXT = 24; |
| private static final int MENU_COPY_TO_SDCARD = 25; |
| private static final int MENU_INSERT_SMILEY = 26; |
| private static final int MENU_ADD_ADDRESS_TO_CONTACTS = 27; |
| |
| private static final int SUBJECT_MAX_LENGTH = 40; |
| private static final int RECIPIENTS_MAX_LENGTH = 312; |
| |
| private static final int MESSAGE_LIST_QUERY_TOKEN = 9527; |
| private static final int THREAD_READ_QUERY_TOKEN = 9696; |
| |
| private static final int DELETE_MESSAGE_TOKEN = 9700; |
| private static final int DELETE_CONVERSATION_TOKEN = 9701; |
| |
| private static final int MMS_THRESHOLD = 4; |
| |
| private static final int CHARS_REMAINING_BEFORE_COUNTER_SHOWN = 10; |
| |
| private static final long NO_DATE_FOR_DIALOG = -1L; |
| |
| |
| private ContentResolver mContentResolver; |
| |
| // The parameters/states of the activity. |
| private long mThreadId; // Database key for the current conversation |
| private String mExternalAddress; // Serialized recipients in the current conversation |
| private boolean mComposeMode; // Should we show the recipients editor on startup? |
| private boolean mExitOnSent; // Should we finish() after sending a message? |
| |
| private View mTopPanel; // View containing the recipient and subject editors |
| private View mBottomPanel; // View containing the text editor, send button, ec. |
| private EditText mTextEditor; // Text editor to type your message into |
| private TextView mTextCounter; // Shows the number of characters used in text editor |
| private Button mSendButton; // Press to detonate |
| |
| private String mMsgText; // Text of message |
| |
| private Cursor mMsgListCursor; // Cursor for messages-in-thread query |
| private final Object mMsgListCursorLock = new Object(); |
| private MsgListQueryHandler mMsgListQueryHandler; |
| |
| private MessageListView mMsgListView; // ListView for messages in this conversation |
| private MessageListAdapter mMsgListAdapter; // and its corresponding ListAdapter |
| |
| private RecipientList mRecipientList; // List of recipients for this conversation |
| private RecipientsEditor mRecipientsEditor; // UI control for editing recipients |
| |
| private boolean mIsKeyboardOpen; // Whether the hardware keyboard is visible |
| private boolean mIsLandscape; // Whether we're in landscape mode |
| |
| private boolean mPossiblePendingNotification; // If the message list has changed, we may have |
| // a pending notification to deal with. |
| |
| private static final int RECIPIENTS_REQUIRE_MMS = (1 << 0); // 1 |
| private static final int HAS_SUBJECT = (1 << 1); // 2 |
| private static final int HAS_ATTACHMENT = (1 << 2); // 4 |
| private static final int LENGTH_REQUIRES_MMS = (1 << 3); // 8 |
| |
| private int mMessageState; // A bitmap of the above indicating different |
| // properties of the message -- any bit set |
| // will require conversion to MMS. |
| |
| private int mMsgCount; // Number of SMS messages required to send the current message. |
| |
| // These fields are only used in MMS compose mode (requiresMms() == true) and should |
| // otherwise be null. |
| private SlideshowModel mSlideshow; |
| private Uri mMessageUri; |
| private EditText mSubjectTextEditor; // Text editor for MMS subject |
| private String mSubject; // MMS subject |
| private AttachmentEditor mAttachmentEditor; |
| private PduPersister mPersister; |
| |
| private AlertDialog mSmileyDialog; |
| |
| //========================================================== |
| // Inner classes |
| //========================================================== |
| |
| private final Handler mAttachmentEditorHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case AttachmentEditor.MSG_EDIT_SLIDESHOW: { |
| Intent intent = new Intent(ComposeMessageActivity.this, |
| SlideshowEditActivity.class); |
| // Need this to make sure mMessageUri is set up. |
| convertMessageIfNeeded(HAS_ATTACHMENT, true); |
| intent.setData(mMessageUri); |
| startActivityForResult(intent, REQUEST_CODE_CREATE_SLIDESHOW); |
| break; |
| } |
| case AttachmentEditor.MSG_SEND_SLIDESHOW: { |
| if (isPreparedForSending()) { |
| ComposeMessageActivity.this.confirmSendMessageIfNeeded(); |
| } |
| break; |
| } |
| case AttachmentEditor.MSG_VIEW_IMAGE: |
| case AttachmentEditor.MSG_PLAY_AUDIO: |
| case AttachmentEditor.MSG_PLAY_VIDEO: |
| case AttachmentEditor.MSG_PLAY_SLIDESHOW: { |
| Intent intent = new Intent(ComposeMessageActivity.this, |
| SlideshowActivity.class); |
| intent.setData(mMessageUri); |
| startActivity(intent); |
| break; |
| } |
| |
| case AttachmentEditor.MSG_REPLACE_IMAGE: |
| case AttachmentEditor.MSG_REPLACE_VIDEO: |
| case AttachmentEditor.MSG_REPLACE_AUDIO: |
| mAttachmentEditor.removeAttachment(); |
| showAddAttachmentDialog(); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| }; |
| |
| private final Handler mMessageListItemHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| String type; |
| switch (msg.what) { |
| case MessageListItem.MSG_LIST_EDIT_MMS: |
| type = "mms"; |
| break; |
| case MessageListItem.MSG_LIST_EDIT_SMS: |
| type = "sms"; |
| break; |
| default: |
| Log.w(TAG, "Unknown message: " + msg.what); |
| return; |
| } |
| |
| MessageItem msgItem = getMessageItem(type, (Long) msg.obj); |
| if (msgItem != null) { |
| editMessageItem(msgItem); |
| int attachmentType = requiresMms() |
| ? MessageUtils.getAttachmentType(mSlideshow) |
| : AttachmentEditor.TEXT_ONLY; |
| drawBottomPanel(attachmentType); |
| } |
| } |
| }; |
| |
| private final OnKeyListener mSubjectKeyListener = new OnKeyListener() { |
| public boolean onKey(View v, int keyCode, KeyEvent event) { |
| if (event.getAction() != KeyEvent.ACTION_DOWN) { |
| return false; |
| } |
| |
| // When the subject editor is empty, press "DEL" to hide the input field. |
| if ((keyCode == KeyEvent.KEYCODE_DEL) && (mSubjectTextEditor.length() == 0)) { |
| mSubjectTextEditor.setVisibility(View.GONE); |
| ComposeMessageActivity.this.hideTopPanelIfNecessary(); |
| convertMessageIfNeeded(HAS_SUBJECT, false); |
| return true; |
| } |
| |
| return false; |
| } |
| }; |
| |
| private final OnKeyListener mEmbeddedTextEditorKeyListener = |
| new OnKeyListener() { |
| public boolean onKey(View v, int keyCode, KeyEvent event) { |
| if ((event.getAction() == KeyEvent.ACTION_DOWN) |
| && (keyCode == KeyEvent.KEYCODE_ENTER) |
| && !event.isShiftPressed()) { |
| if (isPreparedForSending()) { |
| sendMessage(); |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } |
| }; |
| |
| private MessageItem getMessageItem(String type, long msgId) { |
| // Check whether the cursor is valid or not. |
| if (mMsgListCursor.isClosed() |
| || mMsgListCursor.isBeforeFirst() |
| || mMsgListCursor.isAfterLast()) { |
| Log.e(TAG, "Bad cursor.", new RuntimeException()); |
| return null; |
| } |
| |
| return mMsgListAdapter.getCachedMessageItem(type, msgId, mMsgListCursor); |
| } |
| |
| private void resetCounter() { |
| mMsgCount = 1; |
| |
| mTextCounter.setText(""); |
| mTextCounter.setVisibility(View.GONE); |
| } |
| |
| private void updateCounter() { |
| String text = mTextEditor.getText().toString(); |
| |
| int[] params = SmsMessage.calculateLength(text, false); |
| /* SmsMessage.calculateLength returns an int[4] with: |
| * int[0] being the number of SMS's required, |
| * int[1] the number of code units used, |
| * int[2] is the number of code units remaining until the next message. |
| * int[3] is the encoding type that should be used for the message. |
| */ |
| mMsgCount = params[0]; |
| int remainingInCurrentMessage = params[2]; |
| |
| if (mMsgCount > 1 || remainingInCurrentMessage <= CHARS_REMAINING_BEFORE_COUNTER_SHOWN) { |
| // Update the remaining characters and number of messages required. |
| mTextCounter.setText(remainingInCurrentMessage + " / " + mMsgCount); |
| mTextCounter.setVisibility(View.VISIBLE); |
| } else { |
| mTextCounter.setVisibility(View.GONE); |
| } |
| |
| convertMessageIfNeeded(LENGTH_REQUIRES_MMS, mMsgCount >= MMS_THRESHOLD); |
| } |
| |
| private void initMmsComponents() { |
| // Initialize subject editor. |
| mSubjectTextEditor = (EditText) findViewById(R.id.subject); |
| mSubjectTextEditor.setOnKeyListener(mSubjectKeyListener); |
| mSubjectTextEditor.setFilters(new InputFilter[] { |
| new InputFilter.LengthFilter(SUBJECT_MAX_LENGTH) }); |
| if (!TextUtils.isEmpty(mSubject)) { |
| mSubjectTextEditor.setText(mSubject); |
| } |
| |
| try { |
| if (mMessageUri != null) { |
| // Move the message into Draft before editing it. |
| mMessageUri = mPersister.move(mMessageUri, Mms.Draft.CONTENT_URI); |
| mSlideshow = SlideshowModel.createFromMessageUri(this, mMessageUri); |
| } else { |
| mSlideshow = createNewMessage(this); |
| if (mMsgText != null) { |
| mSlideshow.get(0).getText().setText(mMsgText); |
| } |
| mMessageUri = createTemporaryMmsMessage(); |
| } |
| } catch (MmsException e) { |
| Log.e(TAG, e.getMessage(), e); |
| finish(); |
| return; |
| } |
| |
| // Set up the attachment editor. |
| mAttachmentEditor = new AttachmentEditor(this, mAttachmentEditorHandler, |
| findViewById(R.id.attachment_editor)); |
| mAttachmentEditor.setOnAttachmentChangedListener(this); |
| |
| int attachmentType = MessageUtils.getAttachmentType(mSlideshow); |
| if (attachmentType == AttachmentEditor.EMPTY) { |
| fixEmptySlideshow(mSlideshow); |
| attachmentType = AttachmentEditor.TEXT_ONLY; |
| } |
| mAttachmentEditor.setAttachment(mSlideshow, attachmentType); |
| } |
| |
| synchronized private void uninitMmsComponents() { |
| // Get text from slideshow if needed. |
| if (mAttachmentEditor != null) { |
| int attachmentType = mAttachmentEditor.getAttachmentType(); |
| if (AttachmentEditor.TEXT_ONLY == attachmentType && mSlideshow != null) { |
| SlideModel model = mSlideshow.get(0); |
| if (model != null) { |
| TextModel textModel = model.getText(); |
| if (textModel != null) { |
| mMsgText = textModel.getText(); |
| } |
| } |
| } |
| } |
| |
| mMessageState = 0; |
| mSlideshow = null; |
| if (mMessageUri != null) { |
| // Not sure if this is the best way to do this.. |
| if (mMessageUri.toString().startsWith(Mms.Draft.CONTENT_URI.toString())) { |
| SqliteWrapper.delete(this, mContentResolver, mMessageUri, null, null); |
| mMessageUri = null; |
| } |
| } |
| if (mSubjectTextEditor != null) { |
| mSubjectTextEditor.setText(""); |
| mSubjectTextEditor.setVisibility(View.GONE); |
| hideTopPanelIfNecessary(); |
| mSubjectTextEditor = null; |
| } |
| mSubject = null; |
| mAttachmentEditor = null; |
| } |
| |
| synchronized private void refreshMmsComponents() { |
| mMessageState = RECIPIENTS_REQUIRE_MMS; |
| if (mSubjectTextEditor != null) { |
| mSubjectTextEditor.setText(""); |
| mSubjectTextEditor.setVisibility(View.GONE); |
| } |
| mSubject = null; |
| |
| try { |
| mSlideshow = createNewMessage(this); |
| if (mMsgText != null) { |
| mSlideshow.get(0).getText().setText(mMsgText); |
| } |
| mMessageUri = createTemporaryMmsMessage(); |
| } catch (MmsException e) { |
| Log.e(TAG, e.getMessage(), e); |
| finish(); |
| return; |
| } |
| |
| int attachmentType = MessageUtils.getAttachmentType(mSlideshow); |
| if (attachmentType == AttachmentEditor.EMPTY) { |
| fixEmptySlideshow(mSlideshow); |
| attachmentType = AttachmentEditor.TEXT_ONLY; |
| } |
| mAttachmentEditor.setAttachment(mSlideshow, attachmentType); |
| } |
| |
| private boolean requiresMms() { |
| return (mMessageState > 0); |
| } |
| |
| private boolean recipientsRequireMms() { |
| return mRecipientList.containsBcc() || mRecipientList.containsEmail(); |
| } |
| |
| private boolean hasAttachment() { |
| return ((mAttachmentEditor != null) |
| && (mAttachmentEditor.getAttachmentType() > AttachmentEditor.TEXT_ONLY)); |
| } |
| |
| private void updateState(int whichState, boolean set) { |
| if (set) { |
| mMessageState |= whichState; |
| } else { |
| mMessageState &= ~whichState; |
| } |
| } |
| |
| private void convertMessage(boolean toMms) { |
| if (LOCAL_LOGV) { |
| Log.v(TAG, "Message type: " + (requiresMms() ? "MMS" : "SMS") |
| + " -> " + (toMms ? "MMS" : "SMS")); |
| } |
| if (toMms) { |
| // Hide the counter and alert the user with a toast |
| if (mTextCounter != null) { |
| mTextCounter.setVisibility(View.GONE); |
| } |
| initMmsComponents(); |
| } else { |
| uninitMmsComponents(); |
| // Show the counter if necessary |
| updateCounter(); |
| } |
| |
| updateSendButtonState(); |
| } |
| |
| private void toastConvertInfo(boolean toMms) { |
| // If we didn't know whether to convert (e.g. resetting after message |
| // send, we need to notify the user. |
| int resId = toMms ? R.string.converting_to_picture_message |
| : R.string.converting_to_text_message; |
| Toast.makeText(this, resId, Toast.LENGTH_SHORT).show(); |
| } |
| |
| private void convertMessageIfNeeded(int whichState, boolean set) { |
| int oldState = mMessageState; |
| updateState(whichState, set); |
| |
| boolean toMms; |
| // If any bits are set in the new state and none were set in the |
| // old state, we need to convert to MMS. |
| if ((oldState == 0) && (mMessageState != 0)) { |
| toMms = true; |
| } else if ((oldState != 0) && (mMessageState == 0)) { |
| // Vice versa, to SMS. |
| toMms = false; |
| } else { |
| // If we changed state but didn't change SMS vs. MMS status, |
| // there is nothing to do. |
| return; |
| } |
| |
| toastConvertInfo(toMms); |
| convertMessage(toMms); |
| } |
| |
| private class DeleteMessageListener implements OnClickListener { |
| private final Uri mDeleteUri; |
| private final boolean mDeleteAll; |
| |
| public DeleteMessageListener(Uri uri, boolean all) { |
| mDeleteUri = uri; |
| mDeleteAll = all; |
| } |
| |
| public DeleteMessageListener(long msgId, String type) { |
| if ("mms".equals(type)) { |
| mDeleteUri = ContentUris.withAppendedId( |
| Mms.CONTENT_URI, msgId); |
| } else { |
| mDeleteUri = ContentUris.withAppendedId( |
| Sms.CONTENT_URI, msgId); |
| } |
| mDeleteAll = false; |
| } |
| |
| public void onClick(DialogInterface dialog, int whichButton) { |
| int token = mDeleteAll ? DELETE_CONVERSATION_TOKEN |
| : DELETE_MESSAGE_TOKEN; |
| mMsgListQueryHandler.startDelete(token, |
| null, mDeleteUri, null, null); |
| } |
| } |
| |
| private void discardTemporaryMessage() { |
| if (requiresMms()) { |
| if (mMessageUri != null) { |
| SqliteWrapper.delete(ComposeMessageActivity.this, |
| mContentResolver, mMessageUri, null, null); |
| } |
| } else if (mThreadId > 0) { |
| deleteTemporarySmsMessage(mThreadId); |
| } |
| |
| // Don't save this message as a draft, even if it is only an SMS. |
| mMsgText = ""; |
| } |
| |
| private class DiscardDraftListener implements OnClickListener { |
| public void onClick(DialogInterface dialog, int whichButton) { |
| discardTemporaryMessage(); |
| goToConversationList(); |
| finish(); |
| } |
| } |
| |
| private class SendIgnoreInvalidRecipientListener implements OnClickListener { |
| public void onClick(DialogInterface dialog, int whichButton) { |
| sendMessage(); |
| } |
| } |
| |
| private class CancelSendingListener implements OnClickListener { |
| public void onClick(DialogInterface dialog, int whichButton) { |
| if (isRecipientsEditorVisible()) { |
| mRecipientsEditor.requestFocus(); |
| } |
| } |
| } |
| |
| private void confirmSendMessageIfNeeded() { |
| if (mRecipientList.hasInvalidRecipient()) { |
| if (mRecipientList.hasValidRecipient()) { |
| String title = getResourcesString(R.string.has_invalid_recipient, |
| mRecipientList.getInvalidRecipientString()); |
| new AlertDialog.Builder(this) |
| .setIcon(android.R.drawable.ic_dialog_alert) |
| .setTitle(title) |
| .setMessage(R.string.invalid_recipient_message) |
| .setPositiveButton(R.string.try_to_send, |
| new SendIgnoreInvalidRecipientListener()) |
| .setNegativeButton(R.string.no, new CancelSendingListener()) |
| .show(); |
| } else { |
| new AlertDialog.Builder(this) |
| .setIcon(android.R.drawable.ic_dialog_alert) |
| .setTitle(R.string.cannot_send_message) |
| .setMessage(R.string.cannot_send_message_reason) |
| .setPositiveButton(R.string.yes, new CancelSendingListener()) |
| .show(); |
| } |
| } else { |
| sendMessage(); |
| } |
| } |
| |
| private final OnFocusChangeListener mRecipientsFocusListener = new OnFocusChangeListener() { |
| public void onFocusChange(View v, boolean hasFocus) { |
| if (!hasFocus) { |
| convertMessageIfNeeded(RECIPIENTS_REQUIRE_MMS, recipientsRequireMms()); |
| updateWindowTitle(); |
| } |
| } |
| }; |
| |
| private final TextWatcher mRecipientsWatcher = new TextWatcher() { |
| public void beforeTextChanged(CharSequence s, int start, int count, int after) { |
| } |
| |
| public void onTextChanged(CharSequence s, int start, int before, int count) { |
| // This is a workaround for bug 1609057. Since onUserInteraction() is |
| // not called when the user touches the soft keyboard, we pretend it was |
| // called when textfields changes. This should be removed when the bug |
| // is fixed. |
| onUserInteraction(); |
| } |
| |
| public void afterTextChanged(Editable s) { |
| int oldValidCount = mRecipientList.size(); |
| int oldTotal = mRecipientList.countInvalidRecipients() + oldValidCount; |
| |
| // Bug 1474782 describes a situation in which we send to |
| // the wrong recipient. We have been unable to reproduce this, |
| // but the best theory we have so far is that the contents of |
| // mRecipientList somehow become stale when entering |
| // ComposeMessageActivity via onNewIntent(). This assertion is |
| // meant to catch one possible path to that, of a non-visible |
| // mRecipientsEditor having its TextWatcher fire and refreshing |
| // mRecipientList with its stale contents. |
| if (!isRecipientsEditorVisible()) { |
| IllegalStateException e = new IllegalStateException( |
| "afterTextChanged called with invisible mRecipientsEditor"); |
| // Make sure the crash is uploaded to the service so we |
| // can see if this is happening in the field. |
| Log.e(TAG, "RecipientsWatcher called incorrectly", e); |
| throw e; |
| } |
| |
| // Refresh our local copy of the recipient list. |
| mRecipientList = mRecipientsEditor.getRecipientList(); |
| // If we have gone to zero recipients, disable send button. |
| updateSendButtonState(); |
| |
| // If a recipient has been added or deleted (or an invalid one has become valid), |
| // convert the message if necessary. This causes us to "drop" conversions when |
| // a recipient becomes invalid, but we check again upon losing focus to ensure our |
| // state doesn't get too stale. This keeps us from thrashing around between |
| // valid and invalid when typing in an email address. |
| int newValidCount = mRecipientList.size(); |
| int newTotal = mRecipientList.countInvalidRecipients() + newValidCount; |
| if ((oldTotal != newTotal) || (newValidCount > oldValidCount)) { |
| convertMessageIfNeeded(RECIPIENTS_REQUIRE_MMS, recipientsRequireMms()); |
| } |
| |
| String recipients = s.toString(); |
| if (recipients.endsWith(",") || recipients.endsWith(", ")) { |
| updateWindowTitle(); |
| } |
| } |
| }; |
| |
| private final OnCreateContextMenuListener mRecipientsMenuCreateListener = |
| new OnCreateContextMenuListener() { |
| public void onCreateContextMenu(ContextMenu menu, View v, |
| ContextMenuInfo menuInfo) { |
| if (menuInfo != null) { |
| Recipient r = ((RecipientContextMenuInfo) menuInfo).recipient; |
| RecipientsMenuClickListener l = new RecipientsMenuClickListener(r); |
| |
| menu.setHeaderTitle(r.name); |
| |
| if (r.person_id != -1) { |
| menu.add(0, MENU_VIEW_CONTACT, 0, R.string.menu_view_contact) |
| .setOnMenuItemClickListener(l); |
| } else { |
| menu.add(0, MENU_ADD_TO_CONTACTS, 0, R.string.menu_add_to_contacts) |
| .setOnMenuItemClickListener(l); |
| } |
| } |
| } |
| }; |
| |
| private final class RecipientsMenuClickListener implements MenuItem.OnMenuItemClickListener { |
| private final Recipient mRecipient; |
| |
| RecipientsMenuClickListener(Recipient recipient) { |
| mRecipient = recipient; |
| } |
| |
| public boolean onMenuItemClick(MenuItem item) { |
| switch (item.getItemId()) { |
| // Context menu handlers for the recipients editor. |
| case MENU_VIEW_CONTACT: { |
| Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, |
| mRecipient.person_id); |
| Intent intent = new Intent(Intent.ACTION_VIEW, uri); |
| ComposeMessageActivity.this.startActivity(intent); |
| return true; |
| } |
| case MENU_ADD_TO_CONTACTS: { |
| Intent intent = new Intent(Insert.ACTION, People.CONTENT_URI); |
| if (Recipient.isPhoneNumber(mRecipient.number)) { |
| intent.putExtra(Insert.PHONE, mRecipient.number); |
| } else { |
| intent.putExtra(Insert.EMAIL, mRecipient.number); |
| } |
| ComposeMessageActivity.this.startActivity(intent); |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| private void addPositionBasedMenuItems(ContextMenu menu, View v, ContextMenuInfo menuInfo) { |
| AdapterView.AdapterContextMenuInfo info; |
| |
| try { |
| info = (AdapterView.AdapterContextMenuInfo) menuInfo; |
| } catch (ClassCastException e) { |
| Log.e(TAG, "bad menuInfo"); |
| return; |
| } |
| final int position = info.position; |
| |
| addUriSpecificMenuItems(menu, v, position); |
| } |
| |
| private Uri getSelectedUriFromMessageList(ListView listView, int position) { |
| // If the context menu was opened over a uri, get that uri. |
| MessageListItem msglistItem = (MessageListItem) listView.getChildAt(position); |
| if (msglistItem == null) { |
| // FIXME: Should get the correct view. No such interface in ListView currently |
| // to get the view by position. The ListView.getChildAt(position) cannot |
| // get correct view since the list doesn't create one child for each item. |
| // And if setSelection(position) then getSelectedView(), |
| // cannot get corrent view when in touch mode. |
| return null; |
| } |
| |
| TextView textView; |
| CharSequence text = null; |
| int selStart = -1; |
| int selEnd = -1; |
| |
| //check if message sender is selected |
| textView = (TextView) msglistItem.findViewById(R.id.text_view); |
| if (textView != null) { |
| text = textView.getText(); |
| selStart = textView.getSelectionStart(); |
| selEnd = textView.getSelectionEnd(); |
| } |
| |
| if (selStart == -1) { |
| //sender is not being selected, it may be within the message body |
| textView = (TextView) msglistItem.findViewById(R.id.body_text_view); |
| if (textView != null) { |
| text = textView.getText(); |
| selStart = textView.getSelectionStart(); |
| selEnd = textView.getSelectionEnd(); |
| } |
| } |
| |
| // Check that some text is actually selected, rather than the cursor |
| // just being placed within the TextView. |
| if (selStart != selEnd) { |
| int min = Math.min(selStart, selEnd); |
| int max = Math.max(selStart, selEnd); |
| |
| URLSpan[] urls = ((Spanned) text).getSpans(min, max, |
| URLSpan.class); |
| |
| if (urls.length == 1) { |
| return Uri.parse(urls[0].getURL()); |
| } |
| } |
| |
| //no uri was selected |
| return null; |
| } |
| |
| private void addUriSpecificMenuItems(ContextMenu menu, View v, int position) { |
| Uri uri = getSelectedUriFromMessageList((ListView) v, position); |
| |
| if (uri != null) { |
| Intent intent = new Intent(null, uri); |
| intent.addCategory(Intent.CATEGORY_SELECTED_ALTERNATIVE); |
| menu.addIntentOptions(0, 0, 0, |
| new android.content.ComponentName(this, ComposeMessageActivity.class), |
| null, intent, 0, null); |
| } |
| } |
| |
| private final void addCallAndContactMenuItems( |
| ContextMenu menu, MsgListMenuClickListener l, MessageItem msgItem) { |
| // Add all possible links in the address & message |
| StringBuilder textToSpannify = new StringBuilder(); |
| if (msgItem.mBoxId == Mms.MESSAGE_BOX_INBOX) { |
| textToSpannify.append(msgItem.mAddress + ": "); |
| } |
| textToSpannify.append(msgItem.mBody); |
| |
| SpannableString msg = new SpannableString(textToSpannify.toString()); |
| Linkify.addLinks(msg, Linkify.ALL); |
| ArrayList<String> uris = |
| MessageUtils.extractUris(msg.getSpans(0, msg.length(), URLSpan.class)); |
| |
| while (uris.size() > 0) { |
| String uriString = uris.remove(0); |
| // Remove any dupes so they don't get added to the menu multiple times |
| while (uris.contains(uriString)) { |
| uris.remove(uriString); |
| } |
| |
| int sep = uriString.indexOf(":"); |
| String prefix = null; |
| if (sep >= 0) { |
| prefix = uriString.substring(0, sep); |
| uriString = uriString.substring(sep + 1); |
| } |
| boolean addToContacts = false; |
| if ("mailto".equalsIgnoreCase(prefix)) { |
| String sendEmailString = getString( |
| R.string.menu_send_email).replace("%s", uriString); |
| menu.add(0, MENU_SEND_EMAIL, 0, sendEmailString) |
| .setOnMenuItemClickListener(l) |
| .setIntent(new Intent( |
| Intent.ACTION_VIEW, |
| Uri.parse("mailto:" + uriString))); |
| addToContacts = !haveEmailContact(uriString); |
| } else if ("tel".equalsIgnoreCase(prefix)) { |
| String callBackString = getString( |
| R.string.menu_call_back).replace("%s", uriString); |
| menu.add(0, MENU_CALL_BACK, 0, callBackString) |
| .setOnMenuItemClickListener(l) |
| .setIntent(new Intent( |
| Intent.ACTION_DIAL, |
| Uri.parse("tel:" + uriString))); |
| addToContacts = !isNumberInContacts(uriString); |
| } |
| if (addToContacts) { |
| Intent intent = new Intent(Insert.ACTION, People.CONTENT_URI); |
| if (Recipient.isPhoneNumber(uriString)) { |
| intent.putExtra(Insert.PHONE, uriString); |
| } else { |
| intent.putExtra(Insert.EMAIL, uriString); |
| } |
| |
| String addContactString = getString( |
| R.string.menu_add_address_to_contacts).replace("%s", uriString); |
| menu.add(0, MENU_ADD_ADDRESS_TO_CONTACTS, 0, addContactString) |
| .setOnMenuItemClickListener(l) |
| .setIntent(intent); |
| } |
| } |
| } |
| |
| private boolean haveEmailContact(String emailAddress) { |
| Cursor cursor = SqliteWrapper.query(this, getContentResolver(), |
| Contacts.ContactMethods.CONTENT_EMAIL_URI, |
| new String[] { Contacts.ContactMethods.NAME }, |
| Contacts.ContactMethods.DATA + " = " + DatabaseUtils.sqlEscapeString(emailAddress), |
| null, null); |
| |
| if (cursor != null) { |
| try { |
| while (cursor.moveToNext()) { |
| String name = cursor.getString(0); |
| if (!TextUtils.isEmpty(name)) { |
| return true; |
| } |
| } |
| } finally { |
| cursor.close(); |
| } |
| } |
| return false; |
| } |
| |
| private boolean isNumberInContacts(String phoneNumber) { |
| CallerInfo ci = ContactInfoCache.getInstance().getCallerInfo(this, phoneNumber); |
| return !TextUtils.isEmpty(ci.name); |
| } |
| |
| private final OnCreateContextMenuListener mMsgListMenuCreateListener = |
| new OnCreateContextMenuListener() { |
| public void onCreateContextMenu(ContextMenu menu, View v, |
| ContextMenuInfo menuInfo) { |
| String type = mMsgListCursor.getString(COLUMN_MSG_TYPE); |
| long msgId = mMsgListCursor.getLong(COLUMN_ID); |
| |
| addPositionBasedMenuItems(menu, v, menuInfo); |
| |
| MessageItem msgItem = mMsgListAdapter.getCachedMessageItem(type, msgId, mMsgListCursor); |
| if (msgItem == null) { |
| Log.e(TAG, "Cannot load message item for type = " + type |
| + ", msgId = " + msgId); |
| return; |
| } |
| |
| menu.setHeaderTitle(R.string.message_options); |
| |
| MsgListMenuClickListener l = new MsgListMenuClickListener(); |
| if (msgItem.isMms()) { |
| switch (msgItem.mBoxId) { |
| case Mms.MESSAGE_BOX_INBOX: |
| break; |
| case Mms.MESSAGE_BOX_DRAFTS: |
| case Mms.MESSAGE_BOX_OUTBOX: |
| menu.add(0, MENU_EDIT_MESSAGE, 0, R.string.menu_edit) |
| .setOnMenuItemClickListener(l); |
| break; |
| } |
| switch (msgItem.mAttachmentType) { |
| case AttachmentEditor.TEXT_ONLY: |
| break; |
| case AttachmentEditor.IMAGE_ATTACHMENT: |
| menu.add(0, MENU_VIEW_PICTURE, 0, R.string.view_picture) |
| .setOnMenuItemClickListener(l); |
| if (haveSomethingToCopyToSDCard(msgItem.mMsgId)) { |
| menu.add(0, MENU_COPY_TO_SDCARD, 0, R.string.copy_to_sdcard) |
| .setOnMenuItemClickListener(l); |
| } |
| break; |
| case AttachmentEditor.SLIDESHOW_ATTACHMENT: |
| default: |
| menu.add(0, MENU_VIEW_SLIDESHOW, 0, R.string.view_slideshow) |
| .setOnMenuItemClickListener(l); |
| if (haveSomethingToCopyToSDCard(msgItem.mMsgId)) { |
| menu.add(0, MENU_COPY_TO_SDCARD, 0, R.string.copy_to_sdcard) |
| .setOnMenuItemClickListener(l); |
| } |
| break; |
| } |
| } else { |
| // Message type is sms. |
| if ((msgItem.mBoxId == Sms.MESSAGE_TYPE_OUTBOX) || |
| (msgItem.mBoxId == Sms.MESSAGE_TYPE_FAILED)) { |
| menu.add(0, MENU_EDIT_MESSAGE, 0, R.string.menu_edit) |
| .setOnMenuItemClickListener(l); |
| } |
| } |
| |
| addCallAndContactMenuItems(menu, l, msgItem); |
| |
| // Forward is not available for undownloaded messages. |
| if (msgItem.isDownloaded()) { |
| menu.add(0, MENU_FORWARD_MESSAGE, 0, R.string.menu_forward) |
| .setOnMenuItemClickListener(l); |
| } |
| |
| // It is unclear what would make most sense for copying an MMS message |
| // to the clipboard, so we currently do SMS only. |
| if (msgItem.isSms()) { |
| menu.add(0, MENU_COPY_MESSAGE_TEXT, 0, R.string.copy_message_text) |
| .setOnMenuItemClickListener(l); |
| } |
| |
| menu.add(0, MENU_VIEW_MESSAGE_DETAILS, 0, R.string.view_message_details) |
| .setOnMenuItemClickListener(l); |
| menu.add(0, MENU_DELETE_MESSAGE, 0, R.string.delete_message) |
| .setOnMenuItemClickListener(l); |
| if (msgItem.mDeliveryReport || msgItem.mReadReport) { |
| menu.add(0, MENU_DELIVERY_REPORT, 0, R.string.view_delivery_report) |
| .setOnMenuItemClickListener(l); |
| } |
| } |
| }; |
| |
| private void editMessageItem(MessageItem msgItem) { |
| if ("sms".equals(msgItem.mType)) { |
| editSmsMessageItem(msgItem); |
| } else { |
| editMmsMessageItem(msgItem); |
| } |
| if (MessageListItem.isFailedMessage(msgItem) && mMsgListAdapter.getCount() <= 1) { |
| // For messages with bad addresses, let the user re-edit the recipients. |
| initRecipientsEditor(); |
| } |
| } |
| |
| private void editSmsMessageItem(MessageItem msgItem) { |
| // Delete the old undelivered SMS and load its content. |
| Uri uri = ContentUris.withAppendedId(Sms.CONTENT_URI, msgItem.mMsgId); |
| SqliteWrapper.delete(ComposeMessageActivity.this, |
| mContentResolver, uri, null, null); |
| mMsgText = msgItem.mBody; |
| } |
| |
| private void editMmsMessageItem(MessageItem msgItem) { |
| if (mMessageUri != null) { |
| // Delete the former draft. |
| SqliteWrapper.delete(ComposeMessageActivity.this, |
| mContentResolver, mMessageUri, null, null); |
| } |
| mMessageUri = msgItem.mMessageUri; |
| ContentValues values = new ContentValues(1); |
| values.put(Mms.MESSAGE_BOX, Mms.MESSAGE_BOX_DRAFTS); |
| SqliteWrapper.update(ComposeMessageActivity.this, |
| mContentResolver, mMessageUri, values, null, null); |
| |
| updateState(RECIPIENTS_REQUIRE_MMS, recipientsRequireMms()); |
| if (!TextUtils.isEmpty(msgItem.mSubject)) { |
| mSubject = msgItem.mSubject; |
| updateState(HAS_SUBJECT, true); |
| } |
| |
| if (msgItem.mAttachmentType > AttachmentEditor.TEXT_ONLY) { |
| updateState(HAS_ATTACHMENT, true); |
| } |
| |
| convertMessage(true); |
| if (!TextUtils.isEmpty(mSubject)) { |
| mSubjectTextEditor.setVisibility(View.VISIBLE); |
| mTopPanel.setVisibility(View.VISIBLE); |
| } else { |
| mSubjectTextEditor.setVisibility(View.GONE); |
| hideTopPanelIfNecessary(); |
| } |
| } |
| |
| private void copyToClipboard(String str) { |
| ClipboardManager clip = |
| (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE); |
| clip.setText(str); |
| } |
| |
| /** |
| * Context menu handlers for the message list view. |
| */ |
| private final class MsgListMenuClickListener implements MenuItem.OnMenuItemClickListener { |
| public boolean onMenuItemClick(MenuItem item) { |
| String type = mMsgListCursor.getString(COLUMN_MSG_TYPE); |
| long msgId = mMsgListCursor.getLong(COLUMN_ID); |
| MessageItem msgItem = getMessageItem(type, msgId); |
| |
| if (msgItem == null) { |
| return false; |
| } |
| |
| switch (item.getItemId()) { |
| case MENU_EDIT_MESSAGE: { |
| editMessageItem(msgItem); |
| int attachmentType = requiresMms() |
| ? MessageUtils.getAttachmentType(mSlideshow) |
| : AttachmentEditor.TEXT_ONLY; |
| drawBottomPanel(attachmentType); |
| return true; |
| } |
| case MENU_COPY_MESSAGE_TEXT: { |
| copyToClipboard(msgItem.mBody); |
| return true; |
| } |
| case MENU_FORWARD_MESSAGE: { |
| Uri uri = null; |
| Intent intent = new Intent(ComposeMessageActivity.this, |
| ComposeMessageActivity.class); |
| |
| intent.putExtra("compose_mode", true); |
| intent.putExtra("exit_on_sent", true); |
| if (type.equals("sms")) { |
| uri = ContentUris.withAppendedId( |
| Sms.CONTENT_URI, msgId); |
| intent.putExtra("sms_body", msgItem.mBody); |
| } else { |
| SendReq sendReq = new SendReq(); |
| String subject = getString(R.string.forward_prefix); |
| if (msgItem.mSubject != null) { |
| subject += msgItem.mSubject; |
| } |
| sendReq.setSubject(new EncodedStringValue(subject)); |
| sendReq.setBody(msgItem.mSlideshow.makeCopy( |
| ComposeMessageActivity.this)); |
| |
| try { |
| // Implicitly copy the parts of the message here. |
| uri = mPersister.persist(sendReq, Mms.Draft.CONTENT_URI); |
| } catch (MmsException e) { |
| Log.e(TAG, "Failed to copy message: " + msgItem.mMessageUri, e); |
| Toast.makeText(ComposeMessageActivity.this, |
| R.string.cannot_save_message, Toast.LENGTH_SHORT).show(); |
| return true; |
| } |
| |
| intent.putExtra("msg_uri", uri); |
| intent.putExtra("subject", subject); |
| } |
| startActivityIfNeeded(intent, -1); |
| return true; |
| } |
| case MENU_VIEW_PICTURE: |
| // FIXME: Use SlideshowActivity to view image for the time being. |
| // As described in UI spec, Pressing an inline attachment will |
| // open up the full view of the attachment in its associated app |
| // (here should the pictures app). |
| // But the <ViewImage> would only show images in MediaStore. |
| // Should we save a copy to MediaStore temporarily for displaying? |
| case MENU_VIEW_SLIDESHOW: { |
| Intent intent = new Intent(ComposeMessageActivity.this, |
| SlideshowActivity.class); |
| intent.setData(ContentUris.withAppendedId(Mms.CONTENT_URI, msgId)); |
| startActivity(intent); |
| return true; |
| } |
| case MENU_VIEW_MESSAGE_DETAILS: { |
| String messageDetails = MessageUtils.getMessageDetails( |
| ComposeMessageActivity.this, mMsgListCursor, msgItem.mMessageSize); |
| new AlertDialog.Builder(ComposeMessageActivity.this) |
| .setTitle(R.string.message_details_title) |
| .setMessage(messageDetails) |
| .setPositiveButton(android.R.string.ok, null) |
| .setCancelable(true) |
| .show(); |
| return true; |
| } |
| case MENU_DELETE_MESSAGE: { |
| DeleteMessageListener l = new DeleteMessageListener( |
| msgItem.mMessageUri, false); |
| confirmDeleteDialog(l, false); |
| return true; |
| } |
| case MENU_DELIVERY_REPORT: |
| showDeliveryReport(msgId, type); |
| return true; |
| |
| case MENU_COPY_TO_SDCARD: { |
| int resId = copyMedia(msgId) ? R.string.copy_to_sdcard_success : |
| R.string.copy_to_sdcard_fail; |
| Toast.makeText(ComposeMessageActivity.this, resId, Toast.LENGTH_SHORT).show(); |
| return true; |
| } |
| |
| default: |
| return false; |
| } |
| } |
| } |
| |
| /** |
| * Looks to see if there are any valid parts of the attachment that can be copied to a SD card. |
| * @param msgId |
| */ |
| private boolean haveSomethingToCopyToSDCard(long msgId) { |
| PduBody body; |
| try { |
| body = SlideshowModel.getPduBody(this, |
| ContentUris.withAppendedId(Mms.CONTENT_URI, msgId)); |
| } catch (MmsException e) { |
| Log.e(TAG, e.getMessage(), e); |
| return false; |
| } |
| |
| boolean result = false; |
| int partNum = body.getPartsNum(); |
| for(int i = 0; i < partNum; i++) { |
| PduPart part = body.getPart(i); |
| String type = new String(part.getContentType()); |
| |
| if ((ContentType.isImageType(type) || ContentType.isVideoType(type) || |
| ContentType.isAudioType(type))) { |
| result = true; |
| break; |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Copies media from an Mms to the "download" directory on the SD card |
| * @param msgId |
| */ |
| private boolean copyMedia(long msgId) { |
| PduBody body; |
| boolean result = true; |
| try { |
| body = SlideshowModel.getPduBody(this, ContentUris.withAppendedId(Mms.CONTENT_URI, msgId)); |
| } catch (MmsException e) { |
| Log.e(TAG, e.getMessage(), e); |
| return false; |
| } |
| |
| int partNum = body.getPartsNum(); |
| for(int i = 0; i < partNum; i++) { |
| PduPart part = body.getPart(i); |
| String type = new String(part.getContentType()); |
| |
| if ((ContentType.isImageType(type) || ContentType.isVideoType(type) || |
| ContentType.isAudioType(type))) { |
| result &= copyPart(part); // all parts have to be successful for a valid result. |
| } |
| } |
| return result; |
| } |
| |
| private boolean copyPart(PduPart part) { |
| Uri uri = part.getDataUri(); |
| |
| InputStream input = null; |
| FileOutputStream fout = null; |
| try { |
| input = mContentResolver.openInputStream(uri); |
| if (input instanceof FileInputStream) { |
| FileInputStream fin = (FileInputStream) input; |
| |
| byte[] location = part.getName(); |
| if (location == null) { |
| location = part.getFilename(); |
| } |
| if (location == null) { |
| location = part.getContentLocation(); |
| } |
| |
| // Depending on the location, there may be an |
| // extension already on the name or not |
| String fileName = new String(location); |
| String dir = "/sdcard/download/"; |
| String extension; |
| int index; |
| if ((index = fileName.indexOf(".")) == -1) { |
| String type = new String(part.getContentType()); |
| extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(type); |
| } else { |
| extension = fileName.substring(index + 1, fileName.length()); |
| fileName = fileName.substring(0, index); |
| } |
| |
| File file = getUniqueDestination(dir + fileName, extension); |
| |
| // make sure the path is valid and directories created for this file. |
| File parentFile = file.getParentFile(); |
| if (!parentFile.exists() && !parentFile.mkdirs()) { |
| Log.e(TAG, "[MMS] copyPart: mkdirs for " + parentFile.getPath() + " failed!"); |
| return false; |
| } |
| |
| fout = new FileOutputStream(file); |
| |
| byte[] buffer = new byte[8000]; |
| while(fin.read(buffer) != -1) { |
| fout.write(buffer); |
| } |
| |
| // Notify other applications listening to scanner events |
| // that a media file has been added to the sd card |
| sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, |
| Uri.fromFile(file))); |
| } |
| } catch (IOException e) { |
| // Ignore |
| Log.e(TAG, "IOException caught while opening or reading stream", e); |
| return false; |
| } finally { |
| if (null != input) { |
| try { |
| input.close(); |
| } catch (IOException e) { |
| // Ignore |
| Log.e(TAG, "IOException caught while closing stream", e); |
| return false; |
| } |
| } |
| if (null != fout) { |
| try { |
| fout.close(); |
| } catch (IOException e) { |
| // Ignore |
| Log.e(TAG, "IOException caught while closing stream", e); |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| private File getUniqueDestination(String base, String extension) { |
| File file = new File(base + "." + extension); |
| |
| for (int i = 2; file.exists(); i++) { |
| file = new File(base + "_" + i + "." + extension); |
| } |
| return file; |
| } |
| |
| private void showDeliveryReport(long messageId, String type) { |
| Intent intent = new Intent(this, DeliveryReportActivity.class); |
| intent.putExtra("message_id", messageId); |
| intent.putExtra("message_type", type); |
| |
| startActivity(intent); |
| } |
| |
| private final IntentFilter mHttpProgressFilter = new IntentFilter(PROGRESS_STATUS_ACTION); |
| |
| private final BroadcastReceiver mHttpProgressReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (PROGRESS_STATUS_ACTION.equals(intent.getAction())) { |
| long token = intent.getLongExtra("token", |
| SendingProgressTokenManager.NO_TOKEN); |
| if (token != mThreadId) { |
| return; |
| } |
| |
| int progress = intent.getIntExtra("progress", 0); |
| switch (progress) { |
| case PROGRESS_START: |
| setProgressBarVisibility(true); |
| break; |
| case PROGRESS_ABORT: |
| case PROGRESS_COMPLETE: |
| setProgressBarVisibility(false); |
| break; |
| default: |
| setProgress(100 * progress); |
| } |
| } |
| } |
| }; |
| |
| //========================================================== |
| // Static methods |
| //========================================================== |
| |
| private static SlideshowModel createNewMessage(Context context) { |
| SlideshowModel slideshow = SlideshowModel.createNew(context); |
| SlideModel slide = new SlideModel(slideshow); |
| |
| TextModel text = new TextModel( |
| context, ContentType.TEXT_PLAIN, "text_0.txt", |
| slideshow.getLayout().getTextRegion()); |
| slide.add(text); |
| |
| slideshow.add(slide); |
| return slideshow; |
| } |
| |
| private static EncodedStringValue[] encodeStrings(String[] array) { |
| int count = array.length; |
| if (count > 0) { |
| EncodedStringValue[] encodedArray = new EncodedStringValue[count]; |
| for (int i = 0; i < count; i++) { |
| encodedArray[i] = new EncodedStringValue(array[i]); |
| } |
| return encodedArray; |
| } |
| return null; |
| } |
| |
| // Get the recipients editor ready to be displayed onscreen. |
| private void initRecipientsEditor() { |
| ViewStub stub = (ViewStub)findViewById(R.id.recipients_editor_stub); |
| if (stub != null) { |
| mRecipientsEditor = (RecipientsEditor) stub.inflate(); |
| } else { |
| mRecipientsEditor = (RecipientsEditor)findViewById(R.id.recipients_editor); |
| mRecipientsEditor.setVisibility(View.VISIBLE); |
| } |
| |
| mRecipientsEditor.setAdapter(new RecipientsAdapter(this)); |
| mRecipientsEditor.populate(mRecipientList); |
| mRecipientsEditor.setOnCreateContextMenuListener(mRecipientsMenuCreateListener); |
| mRecipientsEditor.addTextChangedListener(mRecipientsWatcher); |
| mRecipientsEditor.setOnFocusChangeListener(mRecipientsFocusListener); |
| mRecipientsEditor.setFilters(new InputFilter[] { |
| new InputFilter.LengthFilter(RECIPIENTS_MAX_LENGTH) }); |
| mRecipientsEditor.setOnItemClickListener(new AdapterView.OnItemClickListener() { |
| public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
| // After the user selects an item in the pop-up contacts list, move the |
| // focus to the text editor if there is only one recipient. This helps |
| // the common case of selecting one recipient and then typing a message, |
| // but avoids annoying a user who is trying to add five recipients and |
| // keeps having focus stolen away. |
| if (mRecipientList.size() == 1) { |
| mTextEditor.requestFocus(); |
| } |
| } |
| }); |
| |
| mTopPanel.setVisibility(View.VISIBLE); |
| } |
| |
| //========================================================== |
| // Activity methods |
| //========================================================== |
| |
| private static final String[] MMS_DRAFT_PROJECTION = { |
| Mms._ID, // 0 |
| Mms.SUBJECT // 1 |
| }; |
| |
| private static final int MMS_ID_INDEX = 0; |
| private static final int MMS_SUBJECT_INDEX = 1; |
| |
| private Cursor queryMmsDraft(long threadId) { |
| final String selection = Mms.THREAD_ID + " = " + threadId; |
| return SqliteWrapper.query(this, mContentResolver, |
| Mms.Draft.CONTENT_URI, MMS_DRAFT_PROJECTION, |
| selection, null, null); |
| } |
| |
| private void loadMmsDraftIfNeeded() { |
| Cursor cursor = queryMmsDraft(mThreadId); |
| if (cursor != null) { |
| try { |
| if ((cursor.getCount() == 1) && cursor.moveToFirst()) { |
| mMessageUri = ContentUris.withAppendedId(Mms.Draft.CONTENT_URI, |
| cursor.getLong(MMS_ID_INDEX)); |
| mSubject = cursor.getString(MMS_SUBJECT_INDEX); |
| if (!TextUtils.isEmpty(mSubject)) { |
| updateState(HAS_SUBJECT, true); |
| } |
| |
| if (!requiresMms()) { |
| // it is an MMS draft, since it has no subject or |
| // multiple recipients, it must have an attachment |
| updateState(HAS_ATTACHMENT, true); |
| } |
| } |
| } finally { |
| cursor.close(); |
| } |
| } |
| } |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| requestWindowFeature(Window.FEATURE_PROGRESS); |
| setContentView(R.layout.compose_message_activity); |
| setProgressBarVisibility(false); |
| setTitle(""); |
| |
| // Initialize members for UI elements. |
| initResourceRefs(); |
| |
| mContentResolver = getContentResolver(); |
| mPersister = PduPersister.getPduPersister(this); |
| |
| // Read parameters or previously saved state of this activity. |
| initActivityState(savedInstanceState, getIntent()); |
| |
| if (LOCAL_LOGV) { |
| Log.v(TAG, "onCreate(): savedInstanceState = " + savedInstanceState); |
| Log.v(TAG, "onCreate(): intent = " + getIntent()); |
| Log.v(TAG, "onCreate(): mThreadId = " + mThreadId); |
| Log.v(TAG, "onCreate(): mMessageUri = " + mMessageUri); |
| } |
| |
| // Parse the recipient list. |
| mRecipientList = RecipientList.from(mExternalAddress, this); |
| updateState(RECIPIENTS_REQUIRE_MMS, recipientsRequireMms()); |
| |
| if (ConversationList.isFailedToDeliver(getIntent())) { |
| // Show a pop-up dialog to inform user the message was |
| // failed to deliver. |
| undeliveredMessageDialog(getMessageDate(mMessageUri)); |
| } |
| loadMmsDraftIfNeeded(); |
| |
| // Initialize MMS-specific stuff if we need to. |
| if ((mMessageUri != null) || requiresMms()) { |
| convertMessage(true); |
| |
| if (!TextUtils.isEmpty(mSubject)) { |
| mSubjectTextEditor.setVisibility(View.VISIBLE); |
| mTopPanel.setVisibility(View.VISIBLE); |
| } else { |
| mSubjectTextEditor.setVisibility(View.GONE); |
| hideTopPanelIfNecessary(); |
| } |
| } else if (isEmptySms()) { |
| mMsgText = readTemporarySmsMessage(mThreadId); |
| } |
| |
| // If we are in an existing thread and we are not in "compose mode", |
| // start up the message list view. |
| boolean initRecipients = false; |
| if ((mThreadId > 0L) && !mComposeMode) { |
| initMessageList(false); |
| } else { |
| // Otherwise, show the recipients editor. |
| initRecipients = true; |
| } |
| if (initRecipients || (ConversationList.isFailedToDeliver(getIntent()) |
| && mMsgListAdapter.getCount() <= 1)) { |
| initRecipientsEditor(); |
| } |
| |
| int attachmentType = requiresMms() |
| ? MessageUtils.getAttachmentType(mSlideshow) |
| : AttachmentEditor.TEXT_ONLY; |
| |
| updateSendButtonState(); |
| |
| handleSendIntent(getIntent()); |
| |
| drawBottomPanel(attachmentType); |
| |
| mTopPanel.setFocusable(false); |
| |
| Configuration config = getResources().getConfiguration(); |
| mIsKeyboardOpen = config.keyboardHidden == KEYBOARDHIDDEN_NO; |
| mIsLandscape = config.orientation == Configuration.ORIENTATION_LANDSCAPE; |
| onKeyboardStateChanged(mIsKeyboardOpen); |
| |
| if (TRACE) { |
| android.os.Debug.startMethodTracing("compose"); |
| } |
| } |
| |
| private void hideTopPanelIfNecessary() { |
| if (!isSubjectEditorVisible() && !isRecipientsEditorVisible()) { |
| mTopPanel.setVisibility(View.GONE); |
| } |
| } |
| |
| @Override |
| protected void onNewIntent(Intent intent) { |
| setIntent(intent); |
| |
| long oldThreadId = mThreadId; |
| boolean oldIsMms = requiresMms(); |
| mMessageState = 0; |
| String oldText = mMsgText; |
| |
| // Read parameters or previously saved state of this activity. |
| initActivityState(null, intent); |
| |
| if (LOCAL_LOGV) { |
| Log.v(TAG, "onNewIntent(): intent = " + getIntent()); |
| Log.v(TAG, "onNewIntent(): mThreadId = " + mThreadId); |
| Log.v(TAG, "onNewIntent(): mMessageUri = " + mMessageUri); |
| } |
| |
| if (mThreadId != oldThreadId) { |
| // Save the old message as a draft. |
| if (oldIsMms) { |
| // Save the old temporary message if necessary. |
| if ((mMessageUri != null) && isPreparedForSending()) { |
| updateTemporaryMmsMessage(false); |
| } |
| } else { |
| if (oldThreadId <= 0) { |
| oldThreadId = getOrCreateThreadId(mRecipientList.getToNumbers()); |
| } |
| updateTemporarySmsMessage(oldThreadId, oldText); |
| } |
| |
| // Refresh the recipient list. |
| mRecipientList = RecipientList.from(mExternalAddress, this); |
| updateState(RECIPIENTS_REQUIRE_MMS, recipientsRequireMms()); |
| |
| if ((mThreadId > 0L) && !mComposeMode) { |
| // If we have already initialized the recipients editor, just |
| // hide it in the display. |
| if (mRecipientsEditor != null) { |
| mRecipientsEditor.setVisibility(View.GONE); |
| hideTopPanelIfNecessary(); |
| } |
| initMessageList(false); |
| } else { |
| initRecipientsEditor(); |
| } |
| |
| boolean isMms = (mMessageUri != null) || requiresMms(); |
| if (isMms != oldIsMms) { |
| convertMessage(isMms); |
| } |
| |
| if (isMms) { |
| // Initialize subject editor. |
| if (!TextUtils.isEmpty(mSubject)) { |
| mSubjectTextEditor.setText(mSubject); |
| mSubjectTextEditor.setVisibility(View.VISIBLE); |
| mTopPanel.setVisibility(View.VISIBLE); |
| } else { |
| mSubjectTextEditor.setVisibility(View.GONE); |
| hideTopPanelIfNecessary(); |
| } |
| |
| try { |
| mSlideshow = createNewMessage(this); |
| mMessageUri = createTemporaryMmsMessage(); |
| Toast.makeText(this, R.string.message_saved_as_draft, |
| Toast.LENGTH_SHORT).show(); |
| } catch (MmsException e) { |
| Log.e(TAG, "Cannot create new slideshow and temporary message."); |
| finish(); |
| } |
| } else if (isEmptySms()) { |
| mMsgText = readTemporarySmsMessage(mThreadId); |
| } |
| |
| int attachmentType = requiresMms() ? MessageUtils.getAttachmentType(mSlideshow) |
| : AttachmentEditor.TEXT_ONLY; |
| drawBottomPanel(attachmentType); |
| |
| if (mMsgListCursor != null) { |
| mMsgListCursor.close(); |
| mMsgListCursor = null; |
| } |
| } |
| } |
| |
| @Override |
| protected void onStart() { |
| super.onStart(); |
| |
| updateWindowTitle(); |
| initFocus(); |
| |
| // Register a BroadcastReceiver to listen on HTTP I/O process. |
| registerReceiver(mHttpProgressReceiver, mHttpProgressFilter); |
| |
| if (mMsgListAdapter != null) { |
| mMsgListAdapter.registerObservers(); |
| startMsgListQuery(); |
| } |
| } |
| |
| @Override |
| protected void onResume() { |
| super.onResume(); |
| |
| if (mThreadId > 0 && hasWindowFocus()) { |
| MessageUtils.handleReadReport( |
| ComposeMessageActivity.this, mThreadId, |
| PduHeaders.READ_STATUS_READ, null); |
| MessageUtils.markAsRead(this, mThreadId); |
| } |
| } |
| |
| @Override |
| public void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState(outState); |
| |
| if (mThreadId > 0L) { |
| if (LOCAL_LOGV) { |
| Log.v(TAG, "ONFREEZE: thread_id: " + mThreadId); |
| } |
| outState.putLong("thread_id", mThreadId); |
| } |
| |
| if (LOCAL_LOGV) { |
| Log.v(TAG, "ONFREEZE: address: " + mRecipientList.serialize()); |
| } |
| outState.putString("address", mRecipientList.serialize()); |
| |
| if (needSaveAsMms()) { |
| if (isSubjectEditorVisible()) { |
| outState.putString("subject", mSubjectTextEditor.getText().toString()); |
| } |
| |
| if (mMessageUri != null) { |
| if (LOCAL_LOGV) { |
| Log.v(TAG, "ONFREEZE: mMessageUri: " + mMessageUri); |
| } |
| updateTemporaryMmsMessage(false); |
| outState.putParcelable("msg_uri", mMessageUri); |
| } |
| } else { |
| outState.putString("sms_body", mMsgText); |
| if (mThreadId <= 0) { |
| mThreadId = getOrCreateThreadId(mRecipientList.getToNumbers()); |
| } |
| updateTemporarySmsMessage(mThreadId, mMsgText); |
| } |
| |
| if (mComposeMode) { |
| outState.putBoolean("compose_mode", mComposeMode); |
| } |
| |
| if (mExitOnSent) { |
| outState.putBoolean("exit_on_sent", mExitOnSent); |
| } |
| } |
| |
| private boolean isEmptyMessage() { |
| if (requiresMms()) { |
| return isEmptyMms(); |
| } |
| return isEmptySms(); |
| } |
| |
| private boolean isEmptySms() { |
| return TextUtils.isEmpty(mMsgText); |
| } |
| |
| private boolean isEmptyMms() { |
| return !(hasText() || hasSubject() || hasAttachment()); |
| } |
| |
| private boolean needSaveAsMms() { |
| // subject editor is visible without any contents. |
| if ( (mMessageState == HAS_SUBJECT) && !hasSubject()) { |
| convertMessage(false); |
| return false; |
| } |
| return requiresMms(); |
| } |
| |
| @Override |
| protected void onPause() { |
| super.onPause(); |
| |
| if (isFinishing()) { |
| if (hasValidRecipient()) { |
| if (needSaveAsMms()) { |
| if (mMessageUri != null) { |
| if (isEmptyMms()) { |
| SqliteWrapper.delete(ComposeMessageActivity.this, |
| mContentResolver, mMessageUri, null, null); |
| } else { |
| mThreadId = getOrCreateThreadId(mRecipientList.getToNumbers()); |
| updateTemporaryMmsMessage(true); |
| } |
| } |
| } else { |
| if (isEmptySms()) { |
| if (mThreadId > 0) { |
| deleteTemporarySmsMessage(mThreadId); |
| } |
| } else { |
| mThreadId = getOrCreateThreadId(mRecipientList.getToNumbers()); |
| updateTemporarySmsMessage(mThreadId, mMsgText); |
| Toast.makeText(this, R.string.message_saved_as_draft, |
| Toast.LENGTH_SHORT).show(); |
| } |
| } |
| } else { |
| discardTemporaryMessage(); |
| } |
| } |
| } |
| |
| @Override |
| protected void onStop() { |
| super.onStop(); |
| |
| if (mMsgListAdapter != null) { |
| synchronized (mMsgListCursorLock) { |
| mMsgListCursor = null; |
| mMsgListAdapter.changeCursor(null); |
| } |
| } |
| |
| // Cleanup the BroadcastReceiver. |
| unregisterReceiver(mHttpProgressReceiver); |
| } |
| |
| @Override |
| protected void onDestroy() { |
| if (TRACE) { |
| android.os.Debug.stopMethodTracing(); |
| } |
| |
| super.onDestroy(); |
| |
| if (mMsgListCursor != null) { |
| mMsgListCursor.close(); |
| } |
| } |
| |
| @Override |
| public void onConfigurationChanged(Configuration newConfig) { |
| super.onConfigurationChanged(newConfig); |
| if (LOCAL_LOGV) { |
| Log.v(TAG, "onConfigurationChanged: " + newConfig); |
| } |
| |
| mIsKeyboardOpen = newConfig.keyboardHidden == KEYBOARDHIDDEN_NO; |
| mIsLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE; |
| onKeyboardStateChanged(mIsKeyboardOpen); |
| } |
| |
| private void onKeyboardStateChanged(boolean isKeyboardOpen) { |
| // If the keyboard is hidden, don't show focus highlights for |
| // things that cannot receive input. |
| if (isKeyboardOpen) { |
| if (mRecipientsEditor != null) { |
| mRecipientsEditor.setFocusableInTouchMode(true); |
| } |
| if (mSubjectTextEditor != null) { |
| mSubjectTextEditor.setFocusableInTouchMode(true); |
| } |
| mTextEditor.setFocusableInTouchMode(true); |
| mTextEditor.setHint(R.string.type_to_compose_text_enter_to_send); |
| initFocus(); |
| } else { |
| if (mRecipientsEditor != null) { |
| mRecipientsEditor.setFocusable(false); |
| } |
| if (mSubjectTextEditor != null) { |
| mSubjectTextEditor.setFocusable(false); |
| } |
| mTextEditor.setFocusable(false); |
| mTextEditor.setHint(R.string.open_keyboard_to_compose_message); |
| } |
| } |
| |
| @Override |
| public void onUserInteraction() { |
| checkPendingNotification(); |
| } |
| |
| @Override |
| public boolean onKeyDown(int keyCode, KeyEvent event) { |
| switch (keyCode) { |
| case KeyEvent.KEYCODE_DEL: |
| if ((mMsgListAdapter != null) && mMsgListView.isFocused()) { |
| Cursor cursor; |
| try { |
| cursor = (Cursor) mMsgListView.getSelectedItem(); |
| } catch (ClassCastException e) { |
| Log.e(TAG, "Unexpected ClassCastException.", e); |
| return super.onKeyDown(keyCode, event); |
| } |
| |
| if (cursor != null) { |
| DeleteMessageListener l = new DeleteMessageListener( |
| cursor.getLong(COLUMN_ID), |
| cursor.getString(COLUMN_MSG_TYPE)); |
| confirmDeleteDialog(l, false); |
| return true; |
| } |
| } |
| break; |
| case KeyEvent.KEYCODE_DPAD_CENTER: |
| case KeyEvent.KEYCODE_ENTER: |
| if (isPreparedForSending()) { |
| confirmSendMessageIfNeeded(); |
| return true; |
| } |
| break; |
| case KeyEvent.KEYCODE_BACK: |
| exitComposeMessageActivity(new Runnable() { |
| public void run() { |
| finish(); |
| } |
| }); |
| return true; |
| } |
| |
| return super.onKeyDown(keyCode, event); |
| } |
| |
| @Override |
| public boolean onKeyUp(int keyCode, KeyEvent event) { |
| switch (keyCode) { |
| case KeyEvent.KEYCODE_CALL: |
| // We do this in onKeyUp in order to preserve the ability |
| // to longpress SEND. Also, if we do it in onKeyDown, the |
| // Dialer is running by the time the key is released, and |
| // onKeyUp fires and immediately initiates the phone call. |
| if (isRecipientCallable()) { |
| dialRecipient(); |
| // Fall through to allow the default key handler to cancel |
| // its long press timeout. |
| } |
| break; |
| } |
| return super.onKeyUp(keyCode, event); |
| } |
| |
| private void exitComposeMessageActivity(final Runnable exit) { |
| if (mThreadId != -1) { |
| if (isRecipientsEditorVisible()) { |
| if (hasValidRecipient()) { |
| exit.run(); |
| } else { |
| if (isEmptyMessage()) { |
| discardTemporaryMessage(); |
| exit.run(); |
| } else { |
| MessageUtils.showDiscardDraftConfirmDialog(this, |
| new DiscardDraftListener()); |
| } |
| } |
| } else { |
| exit.run(); |
| } |
| } |
| } |
| |
| private void goToConversationList() { |
| finish(); |
| startActivity(new Intent(this, ConversationList.class)); |
| } |
| |
| private boolean isRecipientsEditorVisible() { |
| return (null != mRecipientsEditor) |
| && (View.VISIBLE == mRecipientsEditor.getVisibility()); |
| } |
| |
| private boolean isSubjectEditorVisible() { |
| return (null != mSubjectTextEditor) |
| && (View.VISIBLE == mSubjectTextEditor.getVisibility()); |
| } |
| |
| public void onAttachmentChanged(int newType, int oldType) { |
| drawBottomPanel(newType); |
| if (newType > AttachmentEditor.TEXT_ONLY) { |
| if (!requiresMms() && !mComposeMode) { |
| toastConvertInfo(true); |
| } |
| updateState(HAS_ATTACHMENT, true); |
| } else { |
| convertMessageIfNeeded(HAS_ATTACHMENT, false); |
| } |
| updateSendButtonState(); |
| } |
| |
| // We don't want to show the "call" option unless there is only one |
| // recipient and it's a phone number. |
| private boolean isRecipientCallable() { |
| return (mRecipientList.size() == 1 && !mRecipientList.containsEmail()); |
| } |
| |
| private void dialRecipient() { |
| String number = mRecipientList.getNumbers()[0]; |
| Intent dialIntent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + number)); |
| startActivity(dialIntent); |
| } |
| |
| @Override |
| public boolean onPrepareOptionsMenu(Menu menu) { |
| menu.clear(); |
| |
| if (isRecipientCallable()) { |
| menu.add(0, MENU_CALL_RECIPIENT, 0, R.string.menu_call).setIcon( |
| com.android.internal.R.drawable.ic_menu_call); |
| } |
| |
| menu.add(0, MENU_CONVERSATION_LIST, 0, R.string.all_threads).setIcon( |
| com.android.internal.R.drawable.ic_menu_friendslist); |
| |
| if (isSubjectEditorVisible()) { |
| menu.add(0, MENU_ADD_SUBJECT, 0, R.string.add_subject).setIcon( |
| com.android.internal.R.drawable.ic_menu_edit); |
| } |
| |
| if ((mAttachmentEditor == null) || (mAttachmentEditor.getAttachmentType() == AttachmentEditor.TEXT_ONLY)) { |
| menu.add(0, MENU_ADD_ATTACHMENT, 0, R.string.add_attachment).setIcon( |
| com.android.internal.R.drawable.ic_menu_attachment); |
| } |
| |
| if (isPreparedForSending()) { |
| menu.add(0, MENU_SEND, 0, R.string.send).setIcon(android.R.drawable.ic_menu_send); |
| } |
| |
| if (mThreadId > 0L) { |
| // Removed search as part of b/1205708 |
| //menu.add(0, MENU_SEARCH, 0, R.string.menu_search).setIcon( |
| // R.drawable.ic_menu_search); |
| if ((null != mMsgListCursor) && (mMsgListCursor.getCount() > 0)) { |
| menu.add(0, MENU_DELETE_THREAD, 0, R.string.delete_thread).setIcon( |
| android.R.drawable.ic_menu_delete); |
| } |
| } else { |
| menu.add(0, MENU_DISCARD, 0, R.string.discard).setIcon(android.R.drawable.ic_menu_delete); |
| } |
| |
| menu.add(0, MENU_INSERT_SMILEY, 0, R.string.menu_insert_smiley).setIcon( |
| com.android.internal.R.drawable.ic_menu_emoticons); |
| |
| buildAddAddressToContactMenuItem(menu); |
| return true; |
| } |
| |
| private void buildAddAddressToContactMenuItem(Menu menu) { |
| if (mRecipientList.hasValidRecipient()) { |
| // Look for the first recipient we don't have a contact for and create a menu item to |
| // add the number to contacts. |
| for (String number : mRecipientList.getToNumbers()) { |
| if (Recipient.isPhoneNumber(number) && !isNumberInContacts(number)) { |
| String addContactString = getString( |
| R.string.menu_add_address_to_contacts).replace("%s", number); |
| Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); |
| intent.setType(Contacts.People.CONTENT_ITEM_TYPE); |
| intent.putExtra(Insert.PHONE, number); |
| menu.add(0, MENU_ADD_ADDRESS_TO_CONTACTS, 0, addContactString) |
| .setIntent(intent); |
| break; |
| } |
| } |
| } |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| switch (item.getItemId()) { |
| case MENU_ADD_SUBJECT: |
| convertMessageIfNeeded(HAS_SUBJECT, true); |
| mSubjectTextEditor.setVisibility(View.VISIBLE); |
| mTopPanel.setVisibility(View.VISIBLE); |
| mSubjectTextEditor.requestFocus(); |
| break; |
| case MENU_ADD_ATTACHMENT: |
| // Launch the add-attachment list dialog |
| showAddAttachmentDialog(); |
| break; |
| case MENU_DISCARD: |
| discardTemporaryMessage(); |
| finish(); |
| break; |
| case MENU_SEND: |
| if (isPreparedForSending()) { |
| confirmSendMessageIfNeeded(); |
| } |
| break; |
| case MENU_SEARCH: |
| onSearchRequested(); |
| break; |
| case MENU_DELETE_THREAD: |
| DeleteMessageListener l = new DeleteMessageListener( |
| getThreadUri(), true); |
| confirmDeleteDialog(l, true); |
| break; |
| case MENU_CONVERSATION_LIST: |
| exitComposeMessageActivity(new Runnable() { |
| public void run() { |
| goToConversationList(); |
| } |
| }); |
| break; |
| case MENU_CALL_RECIPIENT: |
| dialRecipient(); |
| break; |
| case MENU_INSERT_SMILEY: |
| showSmileyDialog(); |
| break; |
| |
| case MENU_ADD_ADDRESS_TO_CONTACTS: |
| return false; // so the intent attached to the menu item will get launched. |
| } |
| |
| return true; |
| } |
| |
| private void addAttachment(int type) { |
| switch (type) { |
| case AttachmentTypeSelectorAdapter.ADD_IMAGE: |
| MessageUtils.selectImage(this, REQUEST_CODE_ATTACH_IMAGE); |
| break; |
| |
| case AttachmentTypeSelectorAdapter.TAKE_PICTURE: { |
| Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); |
| startActivityForResult(intent, REQUEST_CODE_TAKE_PICTURE); |
| } |
| break; |
| |
| case AttachmentTypeSelectorAdapter.ADD_VIDEO: |
| MessageUtils.selectVideo(this, REQUEST_CODE_ATTACH_VIDEO); |
| break; |
| |
| case AttachmentTypeSelectorAdapter.RECORD_VIDEO: { |
| Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); |
| intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0); |
| startActivityForResult(intent, REQUEST_CODE_TAKE_VIDEO); |
| } |
| break; |
| |
| case AttachmentTypeSelectorAdapter.ADD_SOUND: |
| MessageUtils.selectAudio(this, REQUEST_CODE_ATTACH_SOUND); |
| break; |
| |
| case AttachmentTypeSelectorAdapter.RECORD_SOUND: |
| MessageUtils.recordSound(this, REQUEST_CODE_RECORD_SOUND); |
| break; |
| |
| case AttachmentTypeSelectorAdapter.ADD_SLIDESHOW: { |
| boolean wasSms = !requiresMms(); |
| |
| // SlideshowEditActivity needs mMessageUri to work with. |
| convertMessageIfNeeded(HAS_ATTACHMENT, true); |
| |
| if (wasSms) { |
| // If we are converting from SMS, make sure the SMS |
| // text message gets imported into the first slide. |
| TextModel text = mSlideshow.get(0).getText(); |
| if (text != null) { |
| text.setText(mMsgText); |
| } |
| } |
| |
| Intent intent = new Intent(this, SlideshowEditActivity.class); |
| intent.setData(mMessageUri); |
| startActivityForResult(intent, REQUEST_CODE_CREATE_SLIDESHOW); |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| private void showAddAttachmentDialog() { |
| AlertDialog.Builder builder = new AlertDialog.Builder(this); |
| builder.setIcon(R.drawable.ic_dialog_attach); |
| builder.setTitle(R.string.add_attachment); |
| |
| AttachmentTypeSelectorAdapter adapter = new AttachmentTypeSelectorAdapter( |
| this, AttachmentTypeSelectorAdapter.MODE_WITH_SLIDESHOW); |
| |
| builder.setAdapter(adapter, new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int which) { |
| addAttachment(which); |
| } |
| }); |
| |
| builder.show(); |
| } |
| |
| @Override |
| protected void onActivityResult(int requestCode, int resultCode, Intent data) { |
| if (LOCAL_LOGV) { |
| Log.v(TAG, "onActivityResult: requestCode=" + requestCode |
| + ", resultCode=" + resultCode + ", data=" + data); |
| } |
| |
| if (resultCode != RESULT_OK) { |
| // Make sure if there was an error that our message |
| // type remains correct. |
| convertMessageIfNeeded(HAS_ATTACHMENT, hasAttachment()); |
| return; |
| } |
| |
| if (!requiresMms()) { |
| convertMessage(true); |
| } |
| |
| switch(requestCode) { |
| case REQUEST_CODE_CREATE_SLIDESHOW: |
| try { |
| // Refresh the slideshow model since it may be changed |
| // by the slideshow editor. |
| mSlideshow = SlideshowModel.createFromMessageUri(this, mMessageUri); |
| } catch (MmsException e) { |
| Log.e(TAG, "Failed to load slideshow from " + mMessageUri); |
| Toast.makeText(this, getString(R.string.cannot_load_message), |
| Toast.LENGTH_SHORT).show(); |
| return; |
| } |
| |
| // Find the most suitable type for the attachment. |
| int attachmentType = MessageUtils.getAttachmentType(mSlideshow); |
| switch (attachmentType) { |
| case AttachmentEditor.EMPTY: |
| fixEmptySlideshow(mSlideshow); |
| attachmentType = AttachmentEditor.TEXT_ONLY; |
| // fall-through |
| case AttachmentEditor.TEXT_ONLY: |
| mAttachmentEditor.setAttachment(mSlideshow, attachmentType); |
| convertMessageIfNeeded(HAS_ATTACHMENT, false); |
| drawBottomPanel(attachmentType); |
| return; |
| default: |
| mAttachmentEditor.setAttachment(mSlideshow, attachmentType); |
| break; |
| } |
| |
| drawBottomPanel(attachmentType); |
| break; |
| |
| case REQUEST_CODE_TAKE_PICTURE: |
| Bitmap bitmap = (Bitmap) data.getParcelableExtra("data"); |
| |
| if (bitmap == null) { |
| Toast.makeText(this, |
| getResourcesString(R.string.failed_to_add_media, getPictureString()), |
| Toast.LENGTH_SHORT).show(); |
| return; |
| } |
| addImage(bitmap); |
| break; |
| |
| case REQUEST_CODE_ATTACH_IMAGE: |
| addImage(data.getData()); |
| break; |
| |
| case REQUEST_CODE_TAKE_VIDEO: |
| case REQUEST_CODE_ATTACH_VIDEO: |
| addVideo(data.getData()); |
| break; |
| |
| case REQUEST_CODE_ATTACH_SOUND: |
| case REQUEST_CODE_RECORD_SOUND: |
| Uri uri; |
| if (requestCode == REQUEST_CODE_ATTACH_SOUND) { |
| uri = (Uri) data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); |
| if (Settings.System.DEFAULT_RINGTONE_URI.equals(uri)) { |
| uri = null; |
| } |
| } else { |
| uri = data.getData(); |
| } |
| |
| if (uri == null) { |
| convertMessageIfNeeded(HAS_ATTACHMENT, hasAttachment()); |
| return; |
| } |
| |
| try { |
| mAttachmentEditor.changeAudio(uri); |
| mAttachmentEditor.setAttachment( |
| mSlideshow, AttachmentEditor.AUDIO_ATTACHMENT); |
| } catch (MmsException e) { |
| Log.e(TAG, "add audio failed", e); |
| Toast.makeText(this, |
| getResourcesString(R.string.failed_to_add_media, getAudioString()), |
| Toast.LENGTH_SHORT).show(); |
| } catch (UnsupportContentTypeException e) { |
| MessageUtils.showErrorDialog(ComposeMessageActivity.this, |
| getResourcesString(R.string.unsupported_media_format, getAudioString()), |
| getResourcesString(R.string.select_different_media, getAudioString())); |
| } catch (ExceedMessageSizeException e) { |
| MessageUtils.showErrorDialog(ComposeMessageActivity.this, |
| getResourcesString(R.string.exceed_message_size_limitation), |
| getResourcesString(R.string.failed_to_add_media, getAudioString())); |
| } |
| break; |
| |
| default: |
| // TODO |
| break; |
| } |
| // Make sure if there was an error that our message |
| // type remains correct. Excludes add image because it may be in async |
| // resize process. |
| if (!requiresMms() && (REQUEST_CODE_ATTACH_IMAGE != requestCode)) { |
| convertMessage(false); |
| } |
| } |
| |
| private void addImage(Bitmap bitmap) { |
| try { |
| addImage(MessageUtils.saveBitmapAsPart(this, mMessageUri, bitmap)); |
| } catch (MmsException e) { |
| handleAddImageFailure(e); |
| } |
| } |
| |
| private final ResizeImageResultCallback mResizeImageCallback = new ResizeImageResultCallback() { |
| public void onResizeResult(PduPart part) { |
| Context context = ComposeMessageActivity.this; |
| Resources r = context.getResources(); |
| |
| if (part == null) { |
| Toast.makeText(context, |
| r.getString(R.string.failed_to_add_media, getPictureString()), |
| Toast.LENGTH_SHORT).show(); |
| return; |
| } |
| |
| convertMessageIfNeeded(HAS_ATTACHMENT, true); |
| try { |
| long messageId = ContentUris.parseId(mMessageUri); |
| Uri newUri = mPersister.persistPart(part, messageId); |
| mAttachmentEditor.changeImage(newUri); |
| mAttachmentEditor.setAttachment( |
| mSlideshow, AttachmentEditor.IMAGE_ATTACHMENT); |
| } catch (MmsException e) { |
| Toast.makeText(context, |
| r.getString(R.string.failed_to_add_media, getPictureString()), |
| Toast.LENGTH_SHORT).show(); |
| } catch (UnsupportContentTypeException e) { |
| MessageUtils.showErrorDialog(context, |
| r.getString(R.string.unsupported_media_format, getPictureString()), |
| r.getString(R.string.select_different_media, getPictureString())); |
| } catch (ResolutionException e) { |
| MessageUtils.showErrorDialog(context, |
| r.getString(R.string.failed_to_resize_image), |
| r.getString(R.string.resize_image_error_information)); |
| } catch (ExceedMessageSizeException e) { |
| MessageUtils.showErrorDialog(context, |
| r.getString(R.string.exceed_message_size_limitation), |
| r.getString(R.string.failed_to_add_media, getPictureString())); |
| } |
| } |
| }; |
| |
| private void addImage(Uri uri) { |
| try { |
| mAttachmentEditor.changeImage(uri); |
| mAttachmentEditor.setAttachment( |
| mSlideshow, AttachmentEditor.IMAGE_ATTACHMENT); |
| } catch (MmsException e) { |
| handleAddImageFailure(e); |
| } catch (UnsupportContentTypeException e) { |
| MessageUtils.showErrorDialog( |
| ComposeMessageActivity.this, |
| getResourcesString(R.string.unsupported_media_format, getPictureString()), |
| getResourcesString(R.string.select_different_media, getPictureString())); |
| } catch (ResolutionException e) { |
| MessageUtils.resizeImageAsync(ComposeMessageActivity.this, |
| uri, mAttachmentEditorHandler, mResizeImageCallback); |
| } catch (ExceedMessageSizeException e) { |
| MessageUtils.showErrorDialog( |
| ComposeMessageActivity.this, |
| getResourcesString(R.string.exceed_message_size_limitation), |
| getResourcesString(R.string.failed_to_add_media, getPictureString())); |
| } |
| } |
| |
| private void handleAddImageFailure(MmsException exception) { |
| Log.e(TAG, "add image failed", exception); |
| Toast.makeText( |
| this, |
| getResourcesString(R.string.failed_to_add_media, getPictureString()), |
| Toast.LENGTH_SHORT).show(); |
| } |
| |
| private void addVideo(Uri uri) { |
| try { |
| mAttachmentEditor.changeVideo(uri); |
| mAttachmentEditor.setAttachment( |
| mSlideshow, AttachmentEditor.VIDEO_ATTACHMENT); |
| } catch (MmsException e) { |
| Log.e(TAG, "add video failed", e); |
| Toast.makeText(this, |
| getResourcesString(R.string.failed_to_add_media, getVideoString()), |
| Toast.LENGTH_SHORT).show(); |
| } catch (UnsupportContentTypeException e) { |
| MessageUtils.showErrorDialog(ComposeMessageActivity.this, |
| getResourcesString(R.string.unsupported_media_format, getVideoString()), |
| getResourcesString(R.string.select_different_media, getVideoString())); |
| } catch (ExceedMessageSizeException e) { |
| MessageUtils.showErrorDialog(ComposeMessageActivity.this, |
| getResourcesString(R.string.exceed_message_size_limitation), |
| getResourcesString(R.string.failed_to_add_media, getVideoString())); |
| } |
| } |
| |
| private void handleSendIntent(Intent intent) { |
| Bundle extras = intent.getExtras(); |
| |
| if (!Intent.ACTION_SEND.equals(intent.getAction()) || (extras == null)) { |
| return; |
| } |
| |
| if (extras.containsKey(Intent.EXTRA_STREAM)) { |
| Uri uri = (Uri)extras.getParcelable(Intent.EXTRA_STREAM); |
| if (uri != null) { |
| convertMessage(true); |
| if (intent.getType().startsWith("image/")) { |
| addImage(uri); |
| } else if (intent.getType().startsWith("video/")) { |
| addVideo(uri); |
| } |
| } |
| } else if (extras.containsKey(Intent.EXTRA_TEXT)) { |
| mMsgText = extras.getString(Intent.EXTRA_TEXT); |
| } |
| } |
| |
| private String getAudioString() { |
| return getResourcesString(R.string.type_audio); |
| } |
| |
| private String getPictureString() { |
| return getResourcesString(R.string.type_picture); |
| } |
| |
| private String getVideoString() { |
| return getResourcesString(R.string.type_video); |
| } |
| |
| private String getResourcesString(int id, String mediaName) { |
| Resources r = getResources(); |
| return r.getString(id, mediaName); |
| } |
| |
| private String getResourcesString(int id) { |
| Resources r = getResources(); |
| return r.getString(id); |
| } |
| |
| private void fixEmptySlideshow(SlideshowModel slideshow) { |
| if (slideshow.size() == 0) { |
| SlideModel slide = new SlideModel(slideshow); |
| slideshow.add(slide); |
| } |
| |
| if (!slideshow.get(0).hasText()) { |
| TextModel text = new TextModel( |
| this, ContentType.TEXT_PLAIN, "text_0.txt", |
| slideshow.getLayout().getTextRegion()); |
| slideshow.get(0).add(text); |
| } |
| } |
| |
| private void drawBottomPanel(int attachmentType) { |
| // Reset the counter for text editor. |
| resetCounter(); |
| |
| switch (attachmentType) { |
| case AttachmentEditor.EMPTY: |
| throw new IllegalArgumentException( |
| "Type of the attachment may not be EMPTY."); |
| case AttachmentEditor.SLIDESHOW_ATTACHMENT: |
| mBottomPanel.setVisibility(View.GONE); |
| findViewById(R.id.attachment_editor).requestFocus(); |
| return; |
| default: |
| mBottomPanel.setVisibility(View.VISIBLE); |
| String text = null; |
| if (requiresMms()) { |
| TextModel tm = mSlideshow.get(0).getText(); |
| if (tm != null) { |
| text = tm.getText(); |
| } |
| } else { |
| text = mMsgText; |
| } |
| |
| if ((text != null) && !text.equals(mTextEditor.getText().toString())) { |
| mTextEditor.setText(text); |
| } |
| } |
| } |
| |
| //========================================================== |
| // Interface methods |
| //========================================================== |
| |
| public void onClick(View v) { |
| if ((v == mSendButton) && isPreparedForSending()) { |
| confirmSendMessageIfNeeded(); |
| } |
| } |
| |
| private final TextWatcher mTextEditorWatcher = new TextWatcher() { |
| public void beforeTextChanged(CharSequence s, int start, int count, int after) { |
| } |
| |
| public void onTextChanged(CharSequence s, int start, int before, int count) { |
| // This is a workaround for bug 1609057. Since onUserInteraction() is |
| // not called when the user touches the soft keyboard, we pretend it was |
| // called when textfields changes. This should be removed when the bug |
| // is fixed. |
| onUserInteraction(); |
| |
| String str = s.toString(); |
| |
| if (requiresMms()) { |
| // Update the content of the text model. |
| TextModel text = mSlideshow.get(0).getText(); |
| if (text == null) { |
| text = new TextModel( |
| ComposeMessageActivity.this, |
| ContentType.TEXT_PLAIN, |
| "text_0.txt", |
| mSlideshow.getLayout().getTextRegion() |
| ); |
| mSlideshow.get(0).add(text); |
| } |
| |
| if (!text.getText().equals(str)) { |
| text.setText(str); |
| } |
| } else { |
| mMsgText = str; |
| } |
| |
| updateSendButtonState(); |
| |
| updateCounter(); |
| } |
| |
| public void afterTextChanged(Editable s) { |
| } |
| }; |
| |
| //========================================================== |
| // Private methods |
| //========================================================== |
| |
| /** |
| * Initialize all UI elements from resources. |
| */ |
| private void initResourceRefs() { |
| mMsgListView = (MessageListView) findViewById(R.id.history); |
| mMsgListView.setDivider(null); // no divider so we look like IM conversation. |
| mBottomPanel = findViewById(R.id.bottom_panel); |
| mTextEditor = (EditText) findViewById(R.id.embedded_text_editor); |
| mTextEditor.setOnKeyListener(mEmbeddedTextEditorKeyListener); |
| mTextEditor.addTextChangedListener(mTextEditorWatcher); |
| mTextCounter = (TextView) findViewById(R.id.text_counter); |
| mSendButton = (Button) findViewById(R.id.send_button); |
| mSendButton.setOnClickListener(this); |
| mTopPanel = findViewById(R.id.recipients_subject_linear); |
| } |
| |
| private void confirmDeleteDialog(OnClickListener listener, boolean allMessages) { |
| AlertDialog.Builder builder = new AlertDialog.Builder(this); |
| builder.setTitle(R.string.confirm_dialog_title); |
| builder.setIcon(android.R.drawable.ic_dialog_alert); |
| builder.setCancelable(true); |
| builder.setMessage(allMessages |
| ? R.string.confirm_delete_conversation |
| : R.string.confirm_delete_message); |
| builder.setPositiveButton(R.string.yes, listener); |
| builder.setNegativeButton(R.string.no, null); |
| builder.show(); |
| } |
| |
| void undeliveredMessageDialog(long date) { |
| String body; |
| LinearLayout dialog = (LinearLayout) LayoutInflater.from(this).inflate( |
| R.layout.retry_sending_dialog, null); |
| |
| if (date >= 0) { |
| body = getString(R.string.undelivered_msg_dialog_body, |
| MessageUtils.formatTimeStampString(this, date)); |
| } else { |
| // FIXME: we can not get sms retry time. |
| body = getString(R.string.undelivered_sms_dialog_body); |
| } |
| |
| ((TextView) dialog.findViewById(R.id.body_text_view)).setText(body); |
| |
| Toast undeliveredDialog = new Toast(this); |
| undeliveredDialog.setView(dialog); |
| undeliveredDialog.setDuration(Toast.LENGTH_LONG); |
| undeliveredDialog.show(); |
| } |
| |
| private String deriveAddress(Intent intent) { |
| Uri recipientUri = intent.getData(); |
| return (recipientUri == null) |
| ? null : recipientUri.getSchemeSpecificPart(); |
| } |
| |
| private Uri getThreadUri() { |
| return ContentUris.withAppendedId(Threads.CONTENT_URI, mThreadId); |
| } |
| |
| private void startMsgListQuery() { |
| // Cancel any pending queries |
| mMsgListQueryHandler.cancelOperation(MESSAGE_LIST_QUERY_TOKEN); |
| try { |
| // Kick off the new query |
| mMsgListQueryHandler.startQuery( |
| MESSAGE_LIST_QUERY_TOKEN, null, getThreadUri(), |
| PROJECTION, null, null, null); |
| } catch (SQLiteException e) { |
| SqliteWrapper.checkSQLiteException(this, e); |
| } |
| } |
| |
| private void initMessageList(boolean startNewQuery) { |
| // Initialize the list adapter with a null cursor. |
| mMsgListAdapter = new MessageListAdapter( |
| this, null, mMsgListView, true, getThreadType()); |
| mMsgListAdapter.setOnDataSetChangedListener(mDataSetChangedListener); |
| mMsgListAdapter.setMsgListItemHandler(mMessageListItemHandler); |
| mMsgListView.setAdapter(mMsgListAdapter); |
| mMsgListView.setItemsCanFocus(false); |
| mMsgListView.setVisibility(View.VISIBLE); |
| mMsgListView.setOnCreateContextMenuListener(mMsgListMenuCreateListener); |
| mMsgListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { |
| public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
| ((MessageListItem) view).onMessageListItemClick(); |
| } |
| }); |
| |
| // Initialize the async query handler for the message list. |
| if (mMsgListQueryHandler == null) { |
| mMsgListQueryHandler = new MsgListQueryHandler(mContentResolver); |
| } |
| |
| if (startNewQuery) { |
| startMsgListQuery(); |
| } |
| } |
| |
| private Uri createTemporaryMmsMessage() throws MmsException { |
| SendReq sendReq = new SendReq(); |
| fillMessageHeaders(sendReq); |
| PduBody pb = mSlideshow.toPduBody(); |
| sendReq.setBody(pb); |
| Uri res = mPersister.persist(sendReq, Mms.Draft.CONTENT_URI); |
| mSlideshow.sync(pb); |
| return res; |
| } |
| |
| private void updateTemporaryMmsMessage(final boolean showToast) { |
| if (mMessageUri == null) { |
| try { |
| mMessageUri = createTemporaryMmsMessage(); |
| } catch (MmsException e) { |
| Log.e(TAG, "updateTemporaryMmsMessage: cannot createTemporaryMmsMessage.", e); |
| } |
| } else { |
| SendReq sendReq = new SendReq(); |
| fillMessageHeaders(sendReq); |
| mPersister.updateHeaders(mMessageUri, sendReq); |
| final PduBody pb = mSlideshow.toPduBody(); |
| |
| // PduPersister makes database calls and is known to ANR. Do the work on a |
| // background thread. |
| new Thread(new Runnable() { |
| public void run() { |
| synchronized (mPersister) { |
| try { |
| mPersister.updateParts(mMessageUri, pb); |
| if (showToast) { |
| runOnUiThread(new Runnable() { |
| public void run() { |
| Toast.makeText(ComposeMessageActivity.this, |
| R.string.message_saved_as_draft, |
| Toast.LENGTH_SHORT).show(); |
| } |
| }); |
| } |
| } catch (MmsException e) { |
| Log.e(TAG, "updateTemporaryMmsMessage: cannot update message.", e); |
| } |
| } |
| } |
| }).run(); |
| |
| mSlideshow.sync(pb); |
| } |
| deleteTemporarySmsMessage(mThreadId); |
| } |
| |
| private static final String[] SMS_BODY_PROJECTION = { Sms._ID, Sms.BODY }; |
| private static final String SMS_DRAFT_WHERE = Sms.TYPE + "=" + Sms.MESSAGE_TYPE_DRAFT; |
| |
| /** |
| * Reads a draft message for the given thread ID from the database, |
| * if there is one, deletes it from the database, and returns it. |
| * @return The draft message or an empty string. |
| */ |
| private String readTemporarySmsMessage(long thread_id) { |
| // If it's an invalid thread, don't bother. |
| if (thread_id <= 0) { |
| return ""; |
| } |
| |
| Uri thread_uri = ContentUris.withAppendedId(Sms.Conversations.CONTENT_URI, thread_id); |
| String body = ""; |
| |
| Cursor c = SqliteWrapper.query(this, mContentResolver, |
| thread_uri, SMS_BODY_PROJECTION, SMS_DRAFT_WHERE, null, null); |
| if (c != null) { |
| try { |
| if (c.moveToFirst()) { |
| body = c.getString(1); |
| } |
| } finally { |
| c.close(); |
| } |
| } |
| |
| // Clean out drafts for this thread -- if the recipient set changes, |
| // we will lose track of the original draft and be unable to delete |
| // it later. The message will be re-saved if necessary upon exit of |
| // the activity. |
| SqliteWrapper.delete(this, mContentResolver, thread_uri, SMS_DRAFT_WHERE, null); |
| |
| return body; |
| } |
| |
| private void updateTemporarySmsMessage(long thread_id, String contents) { |
| // If we don't have a valid thread, there's nothing to do. |
| if (thread_id <= 0) { |
| return; |
| } |
| |
| // Don't bother saving an empty message. |
| if (TextUtils.isEmpty(contents)) { |
| // But delete the old temporary message if it's there. |
| deleteTemporarySmsMessage(thread_id); |
| return; |
| } |
| Uri thread_uri = ContentUris.withAppendedId(Sms.Conversations.CONTENT_URI, thread_id); |
| Cursor c = SqliteWrapper.query(this, mContentResolver, |
| thread_uri, SMS_BODY_PROJECTION, SMS_DRAFT_WHERE, null, null); |
| |
| if (c.moveToFirst()) { |
| ContentValues values = new ContentValues(1); |
| values.put(Sms.BODY, contents); |
| SqliteWrapper.update(this, mContentResolver, thread_uri, values, SMS_DRAFT_WHERE, null); |
| } else { |
| ContentValues values = new ContentValues(3); |
| values.put(Sms.THREAD_ID, thread_id); |
| values.put(Sms.BODY, contents); |
| values.put(Sms.TYPE, Sms.MESSAGE_TYPE_DRAFT); |
| SqliteWrapper.insert(this, mContentResolver, |
| Sms.CONTENT_URI, values); |
| deleteTemporaryMmsMessage(thread_id); |
| } |
| |
| c.close(); |
| } |
| |
| private void deleteTemporarySmsMessage(long threadId) { |
| SqliteWrapper.delete(this, mContentResolver, |
| ContentUris.withAppendedId(Sms.Conversations.CONTENT_URI, threadId), |
| SMS_DRAFT_WHERE, null); |
| } |
| |
| private void deleteTemporaryMmsMessage(long threadId) { |
| final String where = Mms.THREAD_ID + " = " + threadId; |
| SqliteWrapper.delete(this, mContentResolver, Mms.Draft.CONTENT_URI, where, null); |
| } |
| |
| private String[] fillMessageHeaders(SendReq sendReq) { |
| // Set the numbers in the 'TO' field. |
| String[] dests = mRecipientList.getToNumbers(); |
| EncodedStringValue[] encodedNumbers = encodeStrings(dests); |
| if (encodedNumbers != null) { |
| sendReq.setTo(encodedNumbers); |
| } |
| |
| // Set the numbers in the 'BCC' field. |
| encodedNumbers = encodeStrings(mRecipientList.getBccNumbers()); |
| if (encodedNumbers != null) { |
| sendReq.setBcc(encodedNumbers); |
| } |
| |
| // Set the subject of the message. |
| String subject = (mSubjectTextEditor == null) |
| ? "" : mSubjectTextEditor.getText().toString(); |
| sendReq.setSubject(new EncodedStringValue(subject)); |
| |
| // Update the 'date' field of the message before sending it. |
| sendReq.setDate(System.currentTimeMillis() / 1000L); |
| |
| return dests; |
| } |
| |
| private boolean hasRecipient() { |
| return hasValidRecipient() || hasInvalidRecipient(); |
| } |
| |
| private boolean hasValidRecipient() { |
| // If someone is in the recipient list, or if a valid recipient is |
| // currently in the recipients editor, we have recipients. |
| return (mRecipientList.hasValidRecipient()) |
| || ((mRecipientsEditor != null) |
| && Recipient.isValid(mRecipientsEditor.getText().toString())); |
| } |
| |
| private boolean hasInvalidRecipient() { |
| return (mRecipientList.hasInvalidRecipient()) |
| || ((mRecipientsEditor != null) |
| && !TextUtils.isEmpty(mRecipientsEditor.getText().toString()) |
| && !Recipient.isValid(mRecipientsEditor.getText().toString())); |
| } |
| |
| private boolean hasText() { |
| return mTextEditor.length() > 0; |
| } |
| |
| private boolean hasSubject() { |
| return (null != mSubjectTextEditor) |
| && !TextUtils.isEmpty(mSubjectTextEditor.getText().toString()); |
| } |
| |
| private boolean isPreparedForSending() { |
| return hasRecipient() && (hasAttachment() || hasText()); |
| } |
| |
| private boolean preSendingMessage() { |
| // Nothing to do here for SMS. |
| |
| // needSaveAsMms will convert a message that is solely a Mms message because it has |
| // an empty subject back into an Sms message. Doesn't notify the user of the conversion. |
| if (!needSaveAsMms() && !requiresMms()) { |
| return true; |
| } |
| |
| // Update contents of the message before sending it. |
| updateTemporaryMmsMessage(false); |
| return true; |
| } |
| |
| private void sendMessage() { |
| boolean failed = false; |
| |
| if (!preSendingMessage()) { |
| return; |
| } |
| |
| String[] dests = fillMessageHeaders(new SendReq()); |
| MessageSender msgSender = requiresMms() |
| ? new MmsMessageSender(this, mMessageUri) |
| : new SmsMessageSender(this, dests, mMsgText, mThreadId); |
| |
| try { |
| if (!msgSender.sendMessage(mThreadId) && (mMessageUri != null)) { |
| // The message was sent through SMS protocol, we should |
| // delete the copy which was previously saved in MMS drafts. |
| SqliteWrapper.delete(this, mContentResolver, mMessageUri, null, null); |
| } |
| } catch (MmsException e) { |
| Log.e(TAG, "Failed to send message: " + mMessageUri, e); |
| // TODO Indicate this error to user(for example, show a warning |
| // icon beside the message. |
| failed = true; |
| } catch (Exception e) { |
| Log.e(TAG, "Failed to send message: " + mMessageUri, e); |
| // TODO Indicate this error to user(for example, show a warning |
| // icon beside the message. |
| failed = true; |
| } finally { |
| if (mExitOnSent) { |
| mMsgText = ""; |
| mMessageUri = null; |
| finish(); |
| } else if (!failed) { |
| postSendingMessage(); |
| } |
| } |
| } |
| |
| private long getOrCreateThreadId(String[] numbers) { |
| HashSet<String> recipients = new HashSet<String>(); |
| recipients.addAll(Arrays.asList(numbers)); |
| return Threads.getOrCreateThreadId(this, recipients); |
| } |
| |
| private void postSendingMessage() { |
| if (!requiresMms()) { |
| // This should not be necessary because we delete the draft |
| // message from the database at the time we read it back, |
| // but I am paranoid. |
| deleteTemporarySmsMessage(mThreadId); |
| } |
| |
| // Make the attachment editor hide its view before we destroy it. |
| if (mAttachmentEditor != null) { |
| mAttachmentEditor.hideView(); |
| } |
| |
| // Focus to the text editor. |
| mTextEditor.requestFocus(); |
| |
| // Setting mMessageUri to null here keeps the conversion back to |
| // SMS from deleting the "unnecessary" MMS in the database. |
| mMessageUri = null; |
| |
| // We have to remove the text change listener while the text editor gets cleared and |
| // we subsequently turn the message back into SMS. When the listener is listening while |
| // doing the clearing, it's fighting to update its counts and itself try and turn |
| // the message one way or the other. |
| mTextEditor.removeTextChangedListener(mTextEditorWatcher); |
| |
| // Clear the text box. |
| TextKeyListener.clear(mTextEditor.getText()); |
| |
| if (0 == (RECIPIENTS_REQUIRE_MMS & mMessageState)) { |
| // Start a new message as an SMS. |
| convertMessage(false); |
| mMsgText = ""; // must clear mMsgText because uninitMmsComponents (called from |
| // convertMessage) resets mMsgText text from the text in the attachment |
| // editor's slideshow. If mMsgText is not cleared, drawBottomPanel |
| // will put mMsgText back into the compose text field. |
| } else { |
| // Start a new message as an MMS |
| refreshMmsComponents(); |
| } |
| |
| drawBottomPanel(AttachmentEditor.TEXT_ONLY); |
| |
| // "Or not", in this case. |
| updateSendButtonState(); |
| |
| String[] numbers = mRecipientList.getToNumbers(); |
| long threadId = getOrCreateThreadId(numbers); |
| if (threadId > 0) { |
| if (mRecipientsEditor != null) { |
| mRecipientsEditor.setVisibility(View.GONE); |
| hideTopPanelIfNecessary(); |
| } |
| |
| if ((mMsgListAdapter == null) || (threadId != mThreadId)) { |
| mThreadId = threadId; |
| initMessageList(true); |
| } |
| } else { |
| Log.e(TAG, "Failed to find/create thread with: " |
| + Arrays.toString(numbers)); |
| finish(); |
| return; |
| } |
| // Our changes are done. Let the listener respond to text changes once again. |
| mTextEditor.addTextChangedListener(mTextEditorWatcher); |
| |
| // Close the soft on-screen keyboard if we're in landscape mode so the user can see the |
| // conversation. |
| if (mIsLandscape) { |
| InputMethodManager inputMethodManager = |
| (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); |
| |
| inputMethodManager.hideSoftInputFromWindow(mTextEditor.getWindowToken(), 0); |
| } |
| } |
| |
| private void updateSendButtonState() { |
| boolean enable = false; |
| if (isPreparedForSending()) { |
| // When the type of attachment is slideshow, we should |
| // also hide the 'Send' button since the slideshow view |
| // already has a 'Send' button embedded. |
| if ((mAttachmentEditor == null) || |
| (mAttachmentEditor.getAttachmentType() != AttachmentEditor.SLIDESHOW_ATTACHMENT)) { |
| enable = true; |
| } else { |
| mAttachmentEditor.setCanSend(true); |
| } |
| } else if (null != mAttachmentEditor){ |
| mAttachmentEditor.setCanSend(false); |
| } |
| |
| mSendButton.setEnabled(enable); |
| mSendButton.setFocusable(enable); |
| } |
| |
| private long getMessageDate(Uri uri) { |
| if (uri != null) { |
| Cursor cursor = SqliteWrapper.query(this, mContentResolver, |
| uri, new String[] { Mms.DATE }, null, null, null); |
| if (cursor != null) { |
| try { |
| if ((cursor.getCount() == 1) && cursor.moveToFirst()) { |
| return cursor.getLong(0) * 1000L; |
| } |
| } finally { |
| cursor.close(); |
| } |
| } |
| } |
| return NO_DATE_FOR_DIALOG; |
| } |
| |
| private void setSubjectFromIntent(Intent intent) { |
| String subject = intent.getStringExtra("subject"); |
| if ( !TextUtils.isEmpty(subject) ) { |
| mSubject = subject; |
| } |
| } |
| |
| private void initActivityState(Bundle savedInstanceState, Intent intent) { |
| if (savedInstanceState != null) { |
| mThreadId = savedInstanceState.getLong("thread_id", 0); |
| mMessageUri = (Uri) savedInstanceState.getParcelable("msg_uri"); |
| mExternalAddress = savedInstanceState.getString("address"); |
| mComposeMode = savedInstanceState.getBoolean("compose_mode", false); |
| mExitOnSent = savedInstanceState.getBoolean("exit_on_sent", false); |
| mSubject = savedInstanceState.getString("subject"); |
| mMsgText = savedInstanceState.getString("sms_body"); |
| } else { |
| mThreadId = intent.getLongExtra("thread_id", 0); |
| mMessageUri = (Uri) intent.getParcelableExtra("msg_uri"); |
| if ((mMessageUri == null) && (mThreadId == 0)) { |
| // If we haven't been given a thread id or a URI in the extras, |
| // get it out of the intent. |
| Uri uri = intent.getData(); |
| if ((uri != null) && (uri.getPathSegments().size() >= 2)) { |
| try { |
| mThreadId = Long.parseLong(uri.getPathSegments().get(1)); |
| } catch (NumberFormatException exception) { |
| Log.e(TAG, "Thread ID must be a Long."); |
| } |
| } |
| } |
| mExternalAddress = intent.getStringExtra("address"); |
| mComposeMode = intent.getBooleanExtra("compose_mode", false); |
| mExitOnSent = intent.getBooleanExtra("exit_on_sent", false); |
| mMsgText = intent.getStringExtra("sms_body"); |
| |
| setSubjectFromIntent(intent); |
| } |
| |
| if (!TextUtils.isEmpty(mSubject)) { |
| updateState(HAS_SUBJECT, true); |
| } |
| |
| // If there was not a body already, start with a blank one. |
| if (mMsgText == null) { |
| mMsgText = ""; |
| } |
| |
| if (mExternalAddress == null) { |
| if (mThreadId > 0L) { |
| mExternalAddress = MessageUtils.getAddressByThreadId( |
| this, mThreadId); |
| } else { |
| mExternalAddress = deriveAddress(intent); |
| // Even if we end up creating a thread here and the user |
| // discards the message, we will clean it up later when we |
| // delete obsolete threads. |
| if (!TextUtils.isEmpty(mExternalAddress)) { |
| mThreadId = Threads.getOrCreateThreadId(this, mExternalAddress); |
| } |
| } |
| } |
| } |
| |
| private int getThreadType() { |
| boolean isMms = (mMessageUri != null) || requiresMms(); |
| |
| return (!isMms |
| && (mRecipientList != null) |
| && (mRecipientList.size() > 1)) |
| ? Threads.BROADCAST_THREAD |
| : Threads.COMMON_THREAD; |
| } |
| |
| private void updateWindowTitle() { |
| StringBuilder sb = new StringBuilder(); |
| Iterator<Recipient> iter = mRecipientList.iterator(); |
| while (iter.hasNext()) { |
| Recipient r = iter.next(); |
| sb.append(r.nameAndNumber).append(", ");; |
| } |
| |
| ContactInfoCache cache = ContactInfoCache.getInstance(); |
| String[] values = mRecipientList.getBccNumbers(); |
| if (values.length > 0) { |
| sb.append("Bcc: "); |
| for (String v : values) { |
| sb.append(cache.getContactName(this, v)).append(", "); |
| } |
| } |
| |
| if (sb.length() > 0) { |
| // Delete the trailing ", " characters. |
| int tail = sb.length() - 2; |
| setTitle(sb.delete(tail, tail + 2).toString()); |
| } else { |
| setTitle(getString(R.string.compose_title)); |
| } |
| } |
| |
| private void initFocus() { |
| if (!mIsKeyboardOpen) { |
| return; |
| } |
| |
| // If the recipients editor is visible, there is nothing in it, |
| // and the text editor is not already focused, focus the |
| // recipients editor. |
| if (isRecipientsEditorVisible() && TextUtils.isEmpty(mRecipientsEditor.getText()) |
| && !mTextEditor.isFocused()) { |
| mRecipientsEditor.requestFocus(); |
| return; |
| } |
| |
| // If we decided not to focus the recipients editor, focus the text editor. |
| mTextEditor.requestFocus(); |
| } |
| |
| private final MessageListAdapter.OnDataSetChangedListener |
| mDataSetChangedListener = new MessageListAdapter.OnDataSetChangedListener() { |
| public void onDataSetChanged(MessageListAdapter adapter) { |
| mPossiblePendingNotification = true; |
| } |
| }; |
| |
| private void checkPendingNotification() { |
| if (mPossiblePendingNotification && hasWindowFocus()) { |
| // start async query so as not to slow down user input |
| Uri.Builder builder = Threads.CONTENT_URI.buildUpon(); |
| builder.appendQueryParameter("simple", "true"); |
| mMsgListQueryHandler.startQuery( |
| THREAD_READ_QUERY_TOKEN, null, builder.build(), |
| new String[] { Threads.READ }, |
| "_id=" + mThreadId, null, null); |
| |
| mPossiblePendingNotification = false; |
| } |
| } |
| |
| private final class MsgListQueryHandler extends AsyncQueryHandler { |
| public MsgListQueryHandler(ContentResolver contentResolver) { |
| super(contentResolver); |
| } |
| |
| @Override |
| protected void onQueryComplete(int token, Object cookie, Cursor cursor) { |
| switch(token) { |
| case MESSAGE_LIST_QUERY_TOKEN: |
| synchronized (mMsgListCursorLock) { |
| if (cursor != null) { |
| mMsgListCursor = cursor; |
| mMsgListAdapter.changeCursor(cursor); |
| } else { |
| if (mMsgListCursor != null) { |
| mMsgListCursor.close(); |
| } |
| Log.e(TAG, "Cannot init the cursor for the message list."); |
| finish(); |
| } |
| |
| // Once we have completed the query for the message history, if |
| // there is nothing in the cursor and we are not composing a new |
| // message, we must be editing a draft in a new conversation. |
| // Show the recipients editor to give the user a chance to add |
| // more people before the conversation begins. |
| if (mMsgListCursor.getCount() == 0 && !isRecipientsEditorVisible()) { |
| initRecipientsEditor(); |
| } |
| |
| // FIXME: freshing layout changes the focused view to an unexpected |
| // one, set it back to TextEditor forcely. |
| mTextEditor.requestFocus(); |
| } |
| return; |
| |
| case THREAD_READ_QUERY_TOKEN: |
| boolean isRead = (cursor.moveToFirst() && (cursor.getInt(0) == 1)); |
| if (!isRead) { |
| MessageUtils.handleReadReport( |
| ComposeMessageActivity.this, mThreadId, |
| PduHeaders.READ_STATUS_READ, null); |
| |
| MessageUtils.markAsRead(ComposeMessageActivity.this, mThreadId); |
| } |
| cursor.close(); |
| return; |
| } |
| } |
| |
| @Override |
| protected void onDeleteComplete(int token, Object cookie, int result) { |
| switch(token) { |
| case DELETE_MESSAGE_TOKEN: |
| case DELETE_CONVERSATION_TOKEN: |
| // Update the notification for new messages since they |
| // may be deleted. |
| MessagingNotification.updateNewMessageIndicator( |
| ComposeMessageActivity.this); |
| // Update the notification for failed messages since they |
| // may be deleted. |
| MessagingNotification.updateSendFailedNotification( |
| ComposeMessageActivity.this); |
| break; |
| } |
| |
| if (token == DELETE_CONVERSATION_TOKEN) { |
| ComposeMessageActivity.this.discardTemporaryMessage(); |
| ComposeMessageActivity.this.finish(); |
| } |
| } |
| } |
| |
| private void showSmileyDialog() { |
| if (mSmileyDialog == null) { |
| int[] icons = SmileyParser.DEFAULT_SMILEY_RES_IDS; |
| String[] names = getResources().getStringArray( |
| SmileyParser.DEFAULT_SMILEY_NAMES); |
| final String[] texts = getResources().getStringArray( |
| SmileyParser.DEFAULT_SMILEY_TEXTS); |
| |
| final int N = names.length; |
| |
| List<Map<String, ?>> entries = new ArrayList<Map<String, ?>>(); |
| for (int i = 0; i < N; i++) { |
| // We might have different ASCII for the same icon, skip it if |
| // the icon is already added. |
| boolean added = false; |
| for (int j = 0; j < i; j++) { |
| if (icons[i] == icons[j]) { |
| added = true; |
| break; |
| } |
| } |
| if (!added) { |
| HashMap<String, Object> entry = new HashMap<String, Object>(); |
| |
| entry. put("icon", icons[i]); |
| entry. put("name", names[i]); |
| entry.put("text", texts[i]); |
| |
| entries.add(entry); |
| } |
| } |
| |
| final SimpleAdapter a = new SimpleAdapter( |
| this, |
| entries, |
| R.layout.smiley_menu_item, |
| new String[] {"icon", "name", "text"}, |
| new int[] {R.id.smiley_icon, R.id.smiley_name, R.id.smiley_text}); |
| SimpleAdapter.ViewBinder viewBinder = new SimpleAdapter.ViewBinder() { |
| public boolean setViewValue(View view, Object data, String textRepresentation) { |
| if (view instanceof ImageView) { |
| Drawable img = getResources().getDrawable((Integer)data); |
| ((ImageView)view).setImageDrawable(img); |
| return true; |
| } |
| return false; |
| } |
| }; |
| a.setViewBinder(viewBinder); |
| |
| AlertDialog.Builder b = new AlertDialog.Builder(this); |
| |
| b.setTitle(getString(R.string.menu_insert_smiley)); |
| |
| b.setCancelable(true); |
| b.setAdapter(a, new DialogInterface.OnClickListener() { |
| public final void onClick(DialogInterface dialog, int which) { |
| HashMap<String, Object> item = (HashMap<String, Object>) a.getItem(which); |
| mTextEditor.append((String)item.get("text")); |
| } |
| }); |
| |
| mSmileyDialog = b.create(); |
| } |
| |
| mSmileyDialog.show(); |
| } |
| } |