com.android.mms.quickmessage.QuickMessagePopup.java Source code

Java tutorial

Introduction

Here is the source code for com.android.mms.quickmessage.QuickMessagePopup.java

Source

/*
 * Copyright (C) 2012 The CyanogenMod Project (DvTonder)
 *
 * 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.quickmessage;

import java.util.ArrayList;
import java.util.Iterator;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.KeyguardManager;
import android.app.LoaderManager;
import android.app.NotificationManager;
import android.content.Context;
import android.content.CursorLoader;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.Loader;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.provider.ContactsContract.Profile;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.text.InputFilter;
import android.text.InputFilter.LengthFilter;
import android.text.InputType;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.CursorAdapter;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.QuickContactBadge;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;

import com.android.internal.telephony.util.BlacklistUtils;
import com.android.mms.MmsConfig;
import com.android.mms.R;
import com.android.mms.data.Contact;
import com.android.mms.data.Conversation;
import com.android.mms.templates.TemplatesProvider.Template;
import com.android.mms.transaction.MessagingNotification;
import com.android.mms.transaction.MessagingNotification.NotificationInfo;
import com.android.mms.transaction.SmsMessageSender;
import com.android.mms.ui.MessageUtils;
import com.android.mms.ui.MessagingPreferenceActivity;
import com.android.mms.util.UnicodeFilter;
import com.google.android.mms.MmsException;
//import com.android.mms.ui.ImageAdapter;
//import com.android.mms.util.EmojiParser;
import com.android.mms.util.SmileyParser;

public class QuickMessagePopup extends Activity implements LoaderManager.LoaderCallbacks<Cursor> {
    private static final String LOG_TAG = "QuickMessagePopup";

    private boolean DEBUG = false;

    // Intent bungle fields
    public static final String SMS_FROM_NAME_EXTRA = "com.android.mms.SMS_FROM_NAME";
    public static final String SMS_FROM_NUMBER_EXTRA = "com.android.mms.SMS_FROM_NUMBER";
    public static final String SMS_NOTIFICATION_OBJECT_EXTRA = "com.android.mms.NOTIFICATION_OBJECT";
    public static final String QR_SHOW_KEYBOARD_EXTRA = "com.android.mms.QR_SHOW_KEYBOARD";

    // Message removal
    public static final String QR_REMOVE_MESSAGES_EXTRA = "com.android.mms.QR_REMOVE_MESSAGES";
    public static final String QR_THREAD_ID_EXTRA = "com.android.mms.QR_THREAD_ID";

    // Templates support
    private static final int DIALOG_TEMPLATE_SELECT = 1;
    private static final int DIALOG_TEMPLATE_NOT_AVAILABLE = 2;
    private SimpleCursorAdapter mTemplatesCursorAdapter;
    private int mNumTemplates = 0;

    // View items
    private ImageView mQmPagerArrow;
    private TextView mQmMessageCounter;
    private Button mCloseButton;
    private Button mViewButton;

    // General items
    private Drawable mDefaultContactImage;
    private Context mContext;
    private boolean mScreenUnlocked = false;
    private KeyguardManager mKeyguardManager = null;
    private PowerManager mPowerManager;

    // Message list items
    private ArrayList<QuickMessage> mMessageList;
    private QuickMessage mCurrentQm = null;
    private int mCurrentPage = -1; // Set to an invalid index

    // Configuration
    private boolean mCloseClosesAll = false;
    private boolean mWakeAndUnlock = false;
    private boolean mDarkTheme = false;
    private boolean mFullTimestamp = false;
    private int mUnicodeStripping = MessagingPreferenceActivity.UNICODE_STRIPPING_LEAVE_INTACT;
    private UnicodeFilter mUnicodeFilter = null;
    private int mInputMethod;

    // Message pager
    private ViewPager mMessagePager;
    private MessagePagerAdapter mPagerAdapter;

    // Options menu items
    private static final int MENU_ADD_TEMPLATE = 1;
    private static final int MENU_ADD_TO_BLACKLIST = 2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Initialise the message list and other variables
        mContext = this;
        mMessageList = new ArrayList<QuickMessage>();
        mDefaultContactImage = getResources().getDrawable(R.drawable.ic_contact_picture);
        mNumTemplates = getTemplatesCount();
        mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);

        // Get the preferences
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
        mFullTimestamp = prefs.getBoolean(MessagingPreferenceActivity.FULL_TIMESTAMP, false);
        mCloseClosesAll = prefs.getBoolean(MessagingPreferenceActivity.QM_CLOSE_ALL_ENABLED, false);
        mWakeAndUnlock = prefs.getBoolean(MessagingPreferenceActivity.QM_LOCKSCREEN_ENABLED, false);
        mDarkTheme = prefs.getBoolean(MessagingPreferenceActivity.QM_DARK_THEME_ENABLED, false);
        mUnicodeStripping = prefs.getInt(MessagingPreferenceActivity.UNICODE_STRIPPING_VALUE,
                MessagingPreferenceActivity.UNICODE_STRIPPING_LEAVE_INTACT);
        mInputMethod = Integer.parseInt(prefs.getString(MessagingPreferenceActivity.INPUT_TYPE,
                Integer.toString(InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)));

        mDarkTheme = mContext.getResources()
                .getConfiguration().uiThemeMode == Configuration.UI_THEME_MODE_HOLO_DARK;

        // Set the window features and layout
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.dialog_quickmessage);

        // Turn on the Options Menu
        invalidateOptionsMenu();

        // Load the views and Parse the intent to show the QuickMessage
        setupViews();
        parseIntent(getIntent().getExtras(), false);

        setFinishOnTouchOutside(false);
    }

    private void setupViews() {

        // Load the main views
        mQmPagerArrow = (ImageView) findViewById(R.id.pager_arrow);
        mQmMessageCounter = (TextView) findViewById(R.id.message_counter);
        mCloseButton = (Button) findViewById(R.id.button_close);
        mViewButton = (Button) findViewById(R.id.button_view);

        // Set the theme color on the pager arrow
        Resources res = getResources();
        if (mDarkTheme) {
            mQmPagerArrow.setBackgroundColor(res.getColor(R.color.quickmessage_body_dark_bg));
        } else {
            mQmPagerArrow.setBackgroundColor(res.getColor(R.color.quickmessage_body_light_bg));
        }

        // ViewPager Support
        mPagerAdapter = new MessagePagerAdapter();
        mMessagePager = (ViewPager) findViewById(R.id.message_pager);
        mMessagePager.setAdapter(mPagerAdapter);
        mMessagePager.setOnPageChangeListener(mPagerAdapter);

        // Close button
        mCloseButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                // If not closing all, close the current QM and move on
                int numMessages = mMessageList.size();
                if (mCloseClosesAll || numMessages == 1) {
                    clearNotification(true);
                    finish();
                } else {
                    // Dismiss the keyboard if it is shown
                    QuickMessage qm = mMessageList.get(mCurrentPage);
                    if (qm != null) {
                        dismissKeyboard(qm);

                        if (mCurrentPage < numMessages - 1) {
                            showNextMessageWithRemove(qm);
                        } else {
                            showPreviousMessageWithRemove(qm);
                        }
                    }
                }
            }
        });

        // View button
        mViewButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {

                // Override the re-lock if the screen was unlocked
                if (mScreenUnlocked) {
                    // Cancel the receiver that will clear the wake locks
                    ClearAllReceiver.removeCancel(getApplicationContext());
                    ClearAllReceiver.clearAll(false);
                    mScreenUnlocked = false;
                }

                // Trigger the view intent
                mCurrentQm = mMessageList.get(mCurrentPage);
                Intent vi = mCurrentQm.getViewIntent();
                if (vi != null) {
                    mCurrentQm.saveReplyText();
                    vi.putExtra("sms_body", mCurrentQm.getReplyText());

                    startActivity(vi);
                }
                clearNotification(false);
                finish();
            }
        });
    }

    private void parseIntent(Bundle extras, boolean newMessage) {
        if (extras == null) {
            return;
        }

        // Check if we are being called to remove messages already showing
        if (extras.getBoolean(QR_REMOVE_MESSAGES_EXTRA, false)) {
            // Get the ID
            long threadId = extras.getLong(QR_THREAD_ID_EXTRA, -1);
            if (threadId != -1) {
                removeMatchingMessages(threadId);
            }
        } else {
            // Parse the intent and ensure we have a notification object to work with
            NotificationInfo nm = (NotificationInfo) extras.getParcelable(SMS_NOTIFICATION_OBJECT_EXTRA);
            if (nm != null) {
                QuickMessage qm = new QuickMessage(extras.getString(SMS_FROM_NAME_EXTRA),
                        extras.getString(SMS_FROM_NUMBER_EXTRA), nm);
                mMessageList.add(qm);
                mPagerAdapter.notifyDataSetChanged();

                // If triggered from Quick Reply the keyboard should be visible immediately
                if (extras.getBoolean(QR_SHOW_KEYBOARD_EXTRA, false)) {
                    getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
                }

                if (newMessage && mCurrentPage != -1) {
                    // There is already a message showing
                    // Stay on the current message
                    mMessagePager.setCurrentItem(mCurrentPage);
                } else {
                    // Set the current message to the last message received
                    mCurrentPage = mMessageList.size() - 1;
                    mMessagePager.setCurrentItem(mCurrentPage);
                }

                if (DEBUG) {
                    Log.d(LOG_TAG,
                            "parseIntent(): New message from " + qm.getThreadId() + " added. Number of messages = "
                                    + mMessageList.size() + ". Displaying page #" + (mCurrentPage + 1));
                }

                // Make sure the counter is accurate
                updateMessageCounter();
            }
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        if (DEBUG) {
            Log.d(LOG_TAG, "onNewIntent() called");
        }

        // Set new intent
        setIntent(intent);

        // Load and display the new message
        parseIntent(intent.getExtras(), true);
        unlockScreen();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }

    @Override
    protected void onResume() {
        super.onResume();

        // Unlock the screen if needed
        unlockScreen();
    }

    @Override
    protected void onStop() {
        super.onStop();

        if (mScreenUnlocked) {
            // Cancel the receiver that will clear the wake locks
            ClearAllReceiver.removeCancel(getApplicationContext());
            ClearAllReceiver.clearAll(true);
        }
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);

        menu.clear();
        // Templates menu item, if there are defined templates
        if (mNumTemplates > 0) {
            menu.add(0, MENU_ADD_TEMPLATE, 0, R.string.template_insert).setIcon(android.R.drawable.ic_menu_add)
                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
        }

        // Add to Blacklist item (if enabled) and we are running on CyanogenMod
        // This allows the app to be run on non-blacklist enabled roms (including Stock)
        if (MessageUtils.isCyanogenMod(this)) {
            if (BlacklistUtils.isBlacklistEnabled(this)) {
                menu.add(0, MENU_ADD_TO_BLACKLIST, 0, R.string.add_to_blacklist)
                        .setIcon(R.drawable.ic_block_message_holo_dark)
                        .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
            }
        }
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case MENU_ADD_TEMPLATE:
            selectTemplate();
            return true;

        case MENU_ADD_TO_BLACKLIST:
            confirmAddBlacklist();
            return true;

        default:
            return super.onContextItemSelected(item);
        }
    }

    //==========================================================
    // Utility methods
    //==========================================================

    /**
     * Copied from ComposeMessageActivity.java, this method displays the available
     * templates and allows the user to select and append it to the reply text. It
     * has been modified to work with this class
     */
    private void selectTemplate() {
        getLoaderManager().restartLoader(0, null, this);
    }

    /**
     * Copied from ComposeMessageActivity.java, this method displays a pop-up a dialog confirming
     * adding the current senders number to the blacklist
     */
    private void confirmAddBlacklist() {
        // Get the sender number
        final String number = mCurrentQm.getFromNumber()[0];
        if (number == null) {
            return;
        }

        // Show dialog
        final String message = getString(R.string.add_to_blacklist_message, number);
        new AlertDialog.Builder(this).setTitle(R.string.add_to_blacklist).setMessage(message)
                .setPositiveButton(R.string.alert_dialog_yes, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int whichButton) {
                        BlacklistUtils.addOrUpdate(getApplicationContext(), number, BlacklistUtils.BLOCK_MESSAGES,
                                BlacklistUtils.BLOCK_MESSAGES);
                    }
                }).setNegativeButton(R.string.alert_dialog_no, null).show();
    }

    /**
     * This method dismisses the on screen keyboard if it is visible for the supplied qm
     *
     * @param qm - qm to check against
     */
    private void dismissKeyboard(QuickMessage qm) {
        if (qm != null) {
            EditText editView = qm.getEditText();
            if (editView != null) {
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(editView.getApplicationWindowToken(), 0);
            }
        }
    }

    /**
     * If 'Wake and unlock' is enabled, this method will unlock the screen
     */
    private void unlockScreen() {
        // See if the lock screen should be disabled
        if (!mWakeAndUnlock) {
            return;
        }

        // See if the screen is locked or if no lock set and the screen is off
        // and get the wake lock to turn on the screen.
        boolean isScreenOn = mPowerManager.isScreenOn();
        boolean inKeyguardRestrictedInputMode = mKeyguardManager.inKeyguardRestrictedInputMode();
        if (inKeyguardRestrictedInputMode || ((!inKeyguardRestrictedInputMode) && !isScreenOn)) {
            ManageWakeLock.acquireFull(mContext);
            mScreenUnlocked = true;
        }
    }

    /**
     * Update the page indicator counter to show the currently selected visible page number
     */
    public void updateMessageCounter() {
        int current = mCurrentPage + 1;
        int total = mMessageList.size();
        mQmMessageCounter.setText(getString(R.string.message_counter, current, total));

        if (DEBUG) {
            Log.d(LOG_TAG, "updateMessageCounter() called, counter text set to " + current + " of " + total);
        }
    }

    /**
     * Remove the supplied qm from the ViewPager and show the previous/older message
     *
     * @param qm
     */
    public void showPreviousMessageWithRemove(QuickMessage qm) {
        if (qm != null) {
            if (DEBUG) {
                Log.d(LOG_TAG, "showPreviousMessageWithRemove()");
            }

            markCurrentMessageRead(qm);
            if (mCurrentPage > 0) {
                updatePages(mCurrentPage - 1, qm);
            }
        }
    }

    /**
     * Remove the supplied qm from the ViewPager and show the next/newer message
     *
     * @param qm
     */
    public void showNextMessageWithRemove(QuickMessage qm) {
        if (qm != null) {
            if (DEBUG) {
                Log.d(LOG_TAG, "showNextMessageWithRemove()");
            }

            markCurrentMessageRead(qm);
            if (mCurrentPage < (mMessageList.size() - 1)) {
                updatePages(mCurrentPage, qm);
            }
        }
    }

    /**
     * Handle qm removal and the move to and display of the appropriate page
     *
     * @param gotoPage - page number to display after the removal
     * @param removeMsg - qm to remove from ViewPager
     */
    private void updatePages(int gotoPage, QuickMessage removeMsg) {
        mMessageList.remove(removeMsg);
        mPagerAdapter.notifyDataSetChanged();
        mMessagePager.setCurrentItem(gotoPage);
        updateMessageCounter();

        if (DEBUG) {
            Log.d(LOG_TAG, "updatePages(): Removed message " + removeMsg.getThreadId() + " and changed to page #"
                    + (gotoPage + 1) + ". Remaining messages = " + mMessageList.size());
        }
    }

    /**
     * Remove all matching quickmessages for the supplied thread id
     *
     * @param threadId
     */
    public void removeMatchingMessages(long threadId) {
        if (DEBUG) {
            Log.d(LOG_TAG, "removeMatchingMessages() looking for match with threadID = " + threadId);
        }

        Iterator<QuickMessage> itr = mMessageList.iterator();
        QuickMessage qmElement = null;

        // Iterate through the list and remove the messages that match
        while (itr.hasNext()) {
            qmElement = itr.next();
            if (qmElement.getThreadId() == threadId) {
                itr.remove();
            }
        }

        // See if there are any remaining messages and update the pager
        if (mMessageList.size() > 0) {
            mPagerAdapter.notifyDataSetChanged();
            mMessagePager.setCurrentItem(1); // First message
            updateMessageCounter();
        } else {
            // we are done
            finish();
        }
    }

    /**
     * Marks the supplied qm as read
     *
     * @param qm
     */
    private void markCurrentMessageRead(QuickMessage qm) {
        if (qm != null) {
            Conversation con = Conversation.get(mContext, qm.getThreadId(), true);
            if (con != null) {
                con.markAsRead(false);
                if (DEBUG) {
                    Log.d(LOG_TAG, "markCurrentMessageRead(): Marked message " + qm.getThreadId() + " as read");
                }
            }
        }
    }

    /**
     * Marks all qm's in the message list as read
     */
    private void markAllMessagesRead() {
        // This iterates through our MessageList and marks the contained threads as read
        for (QuickMessage qm : mMessageList) {
            Conversation con = Conversation.get(mContext, qm.getThreadId(), true);
            if (con != null) {
                con.markAsRead(false);
                if (DEBUG) {
                    Log.d(LOG_TAG, "markAllMessagesRead(): Marked message " + qm.getThreadId() + " as read");
                }
            }
        }
    }

    /**
     * Show the appropriate image for the QuickContact badge
     *
     * @param badge
     * @param addr
     * @param isSelf
     */
    private void updateContactBadge(QuickContactBadge badge, String addr, boolean isSelf) {
        Drawable avatarDrawable;
        if (isSelf || !TextUtils.isEmpty(addr)) {
            Contact contact = isSelf ? Contact.getMe(false) : Contact.get(addr, false);
            avatarDrawable = contact.getAvatar(mContext, mDefaultContactImage);

            if (isSelf) {
                badge.assignContactUri(Profile.CONTENT_URI);
            } else {
                if (contact.existsInDatabase()) {
                    badge.assignContactUri(contact.getUri());
                } else {
                    badge.assignContactFromPhone(contact.getNumber(), true);
                }
            }
        } else {
            avatarDrawable = mDefaultContactImage;
        }
        badge.setImageDrawable(avatarDrawable);
    }

    /**
     * Use standard api to send the supplied message
     *
     * @param message - message to send
     * @param qm - qm to reply to (for sender details)
     */
    private void sendQuickMessage(String message, QuickMessage qm) {
        if (message != null && qm != null) {
            long threadId = qm.getThreadId();
            SmsMessageSender sender = new SmsMessageSender(getBaseContext(), qm.getFromNumber(), message, threadId,
                    qm.getSubId());
            try {
                if (DEBUG) {
                    Log.d(LOG_TAG, "sendQuickMessage(): Sending message to " + qm.getThreadId()
                            + ", with threadID = " + threadId + ". Current page is #" + (mCurrentPage + 1));
                }

                sender.sendMessage(threadId);
                Toast.makeText(mContext, R.string.toast_sending_message, Toast.LENGTH_SHORT).show();
            } catch (MmsException e) {
                Log.e(LOG_TAG, "Error sending message in " + qm.getThreadId());
            }
        }
    }

    /**
     * Clears the status bar notification and, optionally, mark all messages as read
     * This is used to clean up when we are done with all qm's
     *
     * @param markAsRead - should remaining qm's be maked as read?
     */
    private void clearNotification(boolean markAsRead) {
        // Dismiss the notification that brought us here.
        NotificationManager notificationManager = (NotificationManager) getSystemService(
                Context.NOTIFICATION_SERVICE);
        notificationManager.cancel(MessagingNotification.NOTIFICATION_ID);

        // Mark all contained conversations as seen
        if (markAsRead) {
            markAllMessagesRead();
        }

        // Clear the messages list
        mMessageList.clear();
        mPagerAdapter.notifyDataSetChanged();

        if (DEBUG) {
            Log.d(LOG_TAG, "clearNotification(): Message list cleared. Size = " + mMessageList.size());
        }
    }

    /**
     * This method queries the Templates database and returns the count of templates
     *
     * @return - number of templates
     */
    private int getTemplatesCount() {
        Cursor cur = getContentResolver().query(Template.CONTENT_URI, null, null, null, null);
        int numColumns = cur.getCount();
        cur.close();
        return numColumns;
    }

    /**
     * Async data loader used for loading and displaying Templates
     */
    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return new CursorLoader(this, Template.CONTENT_URI, null, null, null, null);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        if (data != null && data.getCount() > 0) {
            showDialog(DIALOG_TEMPLATE_SELECT);
            mTemplatesCursorAdapter.swapCursor(data);
        } else {
            showDialog(DIALOG_TEMPLATE_NOT_AVAILABLE);
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
    }

    /**
     * This displays the Templates selection dialog
     */
    @Override
    protected Dialog onCreateDialog(int id, Bundle args) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        switch (id) {
        case DIALOG_TEMPLATE_NOT_AVAILABLE:
            builder.setTitle(R.string.template_not_present_error_title);
            builder.setMessage(R.string.template_not_present_error);
            return builder.create();

        case DIALOG_TEMPLATE_SELECT:
            builder = new AlertDialog.Builder(this);
            builder.setTitle(R.string.template_select);
            mTemplatesCursorAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, null,
                    new String[] { Template.TEXT }, new int[] { android.R.id.text1 },
                    CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
            builder.setAdapter(mTemplatesCursorAdapter, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    // Get the selected template and text
                    Cursor c = (Cursor) mTemplatesCursorAdapter.getItem(which);
                    String text = c.getString(c.getColumnIndex(Template.TEXT));

                    // Get the currently visible message and append text
                    QuickMessage qm = mMessageList.get(mCurrentPage);
                    if (qm != null) {
                        // insert the template text at the cursor location or replace selected
                        int start = qm.getEditText().getSelectionStart();
                        int end = qm.getEditText().getSelectionEnd();
                        qm.getEditText().getText().replace(Math.min(start, end), Math.max(start, end), text);
                    }
                }
            });
            return builder.create();
        }
        return super.onCreateDialog(id, args);
    }

    //==========================================================
    // Inner classes
    //==========================================================

    /**
     * Message Pager class, used to display and navigate through the ViewPager pages
     */
    private class MessagePagerAdapter extends PagerAdapter implements ViewPager.OnPageChangeListener {

        protected LinearLayout mCurrentPrimaryLayout = null;

        @Override
        public int getCount() {
            return mMessageList.size();
        }

        @Override
        public Object instantiateItem(View collection, int position) {

            // Load the layout to be used
            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View layout;
            if (mDarkTheme) {
                layout = inflater.inflate(R.layout.quickmessage_content_dark, null);
            } else {
                layout = inflater.inflate(R.layout.quickmessage_content_light, null);
            }

            // Load the main views
            EditText qmReplyText = (EditText) layout.findViewById(R.id.embedded_text_editor);
            TextView qmTextCounter = (TextView) layout.findViewById(R.id.text_counter);
            ImageButton qmSendButton = (ImageButton) layout.findViewById(R.id.send_button_sms);
            ImageButton qmTemplatesButton = (ImageButton) layout.findViewById(R.id.templates_button);
            TextView qmMessageText = (TextView) layout.findViewById(R.id.messageTextView);
            TextView qmFromName = (TextView) layout.findViewById(R.id.fromTextView);
            TextView qmTimestamp = (TextView) layout.findViewById(R.id.timestampTextView);
            QuickContactBadge qmContactBadge = (QuickContactBadge) layout.findViewById(R.id.contactBadge);

            // Retrieve the current message
            QuickMessage qm = mMessageList.get(position);
            if (qm != null) {
                if (DEBUG) {
                    Log.d(LOG_TAG, "instantiateItem(): Creating page #" + (position + 1) + " for message in "
                            + qm.getThreadId() + ". Number of pages to create = " + getCount());
                }

                if (mCurrentQm == null) {
                    mCurrentQm = qm;
                }

                // Set the general fields
                qmFromName.setText(qm.getFromName());
                qmTimestamp
                        .setText(MessageUtils.formatTimeStampString(mContext, qm.getTimestamp(), mFullTimestamp));
                updateContactBadge(qmContactBadge, qm.getFromNumber()[0], false);
                SmileyParser parser = SmileyParser.getInstance();
                qmMessageText.setText(parser.addSmileySpans(qm.getMessageBody()));

                if (!mDarkTheme) {
                    // We are using a holo.light background with a holo.dark activity theme
                    // Override the EditText background to use the holo.light theme
                    qmReplyText.setBackgroundResource(R.drawable.edit_text_holo_light);
                }

                // Set the remaining values
                qmReplyText.setInputType(
                        InputType.TYPE_CLASS_TEXT | mInputMethod | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
                                | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
                qmReplyText.setText(qm.getReplyText());
                qmReplyText.setSelection(qm.getReplyText().length());

                if (mUnicodeStripping != MessagingPreferenceActivity.UNICODE_STRIPPING_LEAVE_INTACT) {
                    boolean stripNonDecodableOnly = (MessagingPreferenceActivity.UNICODE_STRIPPING_NON_DECODABLE == mUnicodeStripping);
                    mUnicodeFilter = new UnicodeFilter(stripNonDecodableOnly);
                }

                qmReplyText.addTextChangedListener(new QmTextWatcher(mContext, qmTextCounter, qmSendButton,
                        qmTemplatesButton, mNumTemplates, mUnicodeFilter));
                qmReplyText.setOnEditorActionListener(new OnEditorActionListener() {
                    @Override
                    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                        if (event != null) {
                            // event != null means enter key pressed
                            if (!event.isShiftPressed()) {
                                // if shift is not pressed then move focus to send button
                                if (v != null) {
                                    View focusableView = v.focusSearch(View.FOCUS_RIGHT);
                                    if (focusableView != null) {
                                        focusableView.requestFocus();
                                        return true;
                                    }
                                }
                            }
                            return false;
                        }
                        if (actionId == EditorInfo.IME_ACTION_SEND) {
                            if (v != null) {
                                QuickMessage qm = mMessageList.get(mCurrentPage);
                                if (qm != null) {
                                    sendMessageAndMoveOn(v.getText().toString(), qm);
                                }
                            }
                            return true;
                        }
                        return true;
                    }
                });

                LengthFilter lengthFilter = new LengthFilter(MmsConfig.getMaxTextLimit());
                qmReplyText.setFilters(new InputFilter[] { lengthFilter });

                QmTextWatcher.getQuickReplyCounterText(qmReplyText.getText().toString(), qmTextCounter,
                        qmSendButton, qmTemplatesButton, mNumTemplates);

                // Add the context menu
                registerForContextMenu(qmReplyText);

                // Store the EditText object for future use
                qm.setEditText(qmReplyText);

                // Templates button
                qmTemplatesButton.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        selectTemplate();
                    }
                });

                // Send button
                qmSendButton.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        QuickMessage qm = mMessageList.get(mCurrentPage);
                        if (qm != null) {
                            EditText editView = qm.getEditText();
                            if (editView != null) {
                                sendMessageAndMoveOn(editView.getText().toString(), qm);
                            }
                        }
                    }
                });

                // Add the layout to the viewpager
                ((ViewPager) collection).addView(layout);
            }
            return layout;
        }

        /**
         * This method sends the supplied message in reply to the supplied qm and then
         * moves to the next or previous message as appropriate. If this is the last qm
         * in the MessageList, we end by clearing the notification and calling finish()
         *
         * @param message - message to send
         * @param qm - qm we are replying to (for sender details)
         */
        private void sendMessageAndMoveOn(String message, QuickMessage qm) {
            if (mUnicodeFilter != null) {
                message = mUnicodeFilter.filter(message).toString();
            }
            sendQuickMessage(message, qm);
            // Close the current QM and move on
            int numMessages = mMessageList.size();
            if (numMessages == 1) {
                // No more messages
                clearNotification(true);
                finish();
            } else {
                // Dismiss the keyboard if it is shown
                dismissKeyboard(qm);

                if (mCurrentPage < numMessages - 1) {
                    showNextMessageWithRemove(qm);
                } else {
                    showPreviousMessageWithRemove(qm);
                }
            }
        }

        @Override
        public void setPrimaryItem(ViewGroup container, int position, Object object) {
            LinearLayout view = ((LinearLayout) object);
            if (view != mCurrentPrimaryLayout) {
                mCurrentPrimaryLayout = view;
            }
        }

        @Override
        public void onPageSelected(int position) {
            // The user had scrolled to a new message
            if (mCurrentQm != null) {
                mCurrentQm.saveReplyText();
            }

            // Set the new 'active' QuickMessage
            mCurrentPage = position;
            mCurrentQm = mMessageList.get(position);

            if (DEBUG) {
                Log.d(LOG_TAG, "onPageSelected(): Current page is #" + (position + 1) + " of " + getCount()
                        + " pages. Currenty visible message is from " + mCurrentQm.getThreadId());
            }

            updateMessageCounter();
        }

        @Override
        public int getItemPosition(Object object) {
            // This is needed to force notifyDatasetChanged() to rebuild the pages
            return PagerAdapter.POSITION_NONE;
        }

        @Override
        public void destroyItem(View collection, int position, Object view) {
            ((ViewPager) collection).removeView((LinearLayout) view);
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == ((LinearLayout) object);
        }

        @Override
        public void finishUpdate(View arg0) {
            if (mCurrentQm != null && mCurrentQm.getEditText() != null) {
                // After a page switch, re-focus on the reply editor
                mCurrentQm.getEditText().requestFocus();
            }
        }

        @Override
        public void restoreState(Parcelable arg0, ClassLoader arg1) {
        }

        @Override
        public Parcelable saveState() {
            return null;
        }

        @Override
        public void startUpdate(View arg0) {
            if (mCurrentQm != null) {
                // When the view is refreshed, preserve the current reply
                mCurrentQm.saveReplyText();
            }
        }

        @Override
        public void onPageScrollStateChanged(int arg0) {
        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }
    }

}