org.parandroid.sms.transaction.SmsMessageSender.java Source code

Java tutorial

Introduction

Here is the source code for org.parandroid.sms.transaction.SmsMessageSender.java

Source

/*
 * 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 org.parandroid.sms.transaction;

import com.google.android.mms.MmsException;
import com.google.android.mms.util.SqliteWrapper;

import org.bouncycastle.util.encoders.Base64;
import org.parandroid.encryption.MessageEncryption;
import org.parandroid.encryption.MessageEncryptionFactory;
import org.parandroid.sms.LogTag;
import org.parandroid.sms.MmsConfig;
import org.parandroid.sms.ui.MessageItem;
import org.parandroid.sms.ui.MessageUtils;
import org.parandroid.sms.ui.MessagingPreferenceActivity;

import android.app.PendingIntent;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.provider.Telephony;
import android.provider.Telephony.Mms;
import android.provider.Telephony.Sms;
import android.provider.Telephony.Sms.Inbox;
import android.telephony.SmsManager;
import android.util.Log;

import java.util.ArrayList;

public class SmsMessageSender implements MessageSender {
    private final Context mContext;
    private final int mNumberOfDests;
    private final String[] mDests;
    private final String mMessageText;
    private final String mServiceCenter;
    private final long mThreadId;
    private long mTimestamp;
    private boolean mTryToEncrypt;

    private static final String TAG = "ParandroidSmsMessageSender";

    // Default preference values
    private static final boolean DEFAULT_DELIVERY_REPORT_MODE = false;

    private static final String[] SERVICE_CENTER_PROJECTION = new String[] { Sms.Conversations.REPLY_PATH_PRESENT,
            Sms.Conversations.SERVICE_CENTER, };

    private static final int COLUMN_REPLY_PATH_PRESENT = 0;
    private static final int COLUMN_SERVICE_CENTER = 1;

    public SmsMessageSender(Context context, String[] dests, String msgText, long threadId, boolean tryToEncrypt) {
        mContext = context;
        mMessageText = msgText;
        mNumberOfDests = dests.length;
        mDests = new String[mNumberOfDests];
        System.arraycopy(dests, 0, mDests, 0, mNumberOfDests);
        mTimestamp = System.currentTimeMillis();
        mThreadId = threadId;
        mServiceCenter = getOutgoingServiceCenter(mThreadId);
        mTryToEncrypt = tryToEncrypt;
    }

    public boolean sendMessage(long token) throws MmsException {
        if ((mMessageText == null) || (mNumberOfDests == 0)) {
            // Don't try to send an empty message.
            throw new MmsException("Null message body or dest.");
        }

        SmsManager smsManager = SmsManager.getDefault();

        for (int i = 0; i < mNumberOfDests; i++) {
            boolean isEncrypted = false;

            byte[] encryptedMessage = null;
            if (mTryToEncrypt && MessageEncryptionFactory.hasPublicKey(mContext, mDests[i])) {
                try {
                    encryptedMessage = MessageEncryption.encrypt(mContext, mDests[i], mMessageText);
                    isEncrypted = true;
                } catch (Exception e) {
                    Log.e(TAG, "Error while encrypting message");
                    e.printStackTrace();
                }
            }

            ArrayList<String> messages = null;
            if ((MmsConfig.getEmailGateway() != null)
                    && (Mms.isEmailAddress(mDests[i]) || MessageUtils.isAlias(mDests[i]))) {
                String msgText;
                msgText = mDests[i] + " " + mMessageText;
                mDests[i] = MmsConfig.getEmailGateway();
                messages = smsManager.divideMessage(msgText);
            } else {
                messages = smsManager.divideMessage(mMessageText);
            }
            int messageCount = messages.size();

            if (messageCount == 0) {
                // Don't try to send an empty message.
                throw new MmsException("SmsMessageSender.sendMessage: divideMessage returned "
                        + "empty messages. Original message is \"" + mMessageText + "\"");
            }

            ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>(messageCount);
            ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(messageCount);
            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
            boolean requestDeliveryReport = prefs.getBoolean(MessagingPreferenceActivity.SMS_DELIVERY_REPORT_MODE,
                    DEFAULT_DELIVERY_REPORT_MODE);
            Uri uri = null;
            try {
                if (isEncrypted) {
                    String outboxText = new String(Base64.encode(encryptedMessage));
                    addToParandroidOutbox(i, outboxText);

                } else {
                    uri = Sms.Outbox.addMessage(mContext.getContentResolver(), mDests[i], mMessageText, null,
                            mTimestamp, requestDeliveryReport, mThreadId);
                }
            } catch (SQLiteException e) {
                SqliteWrapper.checkSQLiteException(mContext, e);
            }

            for (int j = 0; j < messageCount; j++) {
                if (requestDeliveryReport) {
                    // TODO: Fix: It should not be necessary to
                    // specify the class in this intent.  Doing that
                    // unnecessarily limits customizability.
                    deliveryIntents.add(PendingIntent.getBroadcast(mContext, 0,
                            new Intent(MessageStatusReceiver.MESSAGE_STATUS_RECEIVED_ACTION, uri, mContext,
                                    MessageStatusReceiver.class),
                            0));
                }
                sentIntents.add(PendingIntent.getBroadcast(mContext, 0,
                        new Intent(SmsReceiverService.MESSAGE_SENT_ACTION, uri, mContext, SmsReceiver.class), 0));
            }

            if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
                log("sendMessage: address[" + i + "]=" + mDests[i] + ", threadId=" + mThreadId + ", uri=" + uri
                        + ", msgs.count=" + messageCount);
            }

            if (!isEncrypted) {
                // we send the msg unencrypted if we don't have the public key, or when the number of
                // messages is larger than 1 (there is no support to send multipart datamessages yet)
                try {
                    smsManager.sendMultipartTextMessage(mDests[i], mServiceCenter, messages, sentIntents,
                            deliveryIntents);
                } catch (Exception ex) {
                    throw new MmsException("SmsMessageSender.sendMessage: caught " + ex
                            + " from SmsManager.sendMultipartTextMessage()");
                }
            } else {
                MultipartDataMessage m = new MultipartDataMessage(MultipartDataMessage.TYPE_MESSAGE, mDests[i],
                        encryptedMessage, sentIntents, deliveryIntents);
                m.send();
            }

        }

        return false;
    }

    private Uri addToParandroidOutbox(int destIndex, String outboxText) {
        outboxText = MultipartDataMessage.MESSAGE_HEADER + outboxText;
        ContentValues values = new ContentValues(7);

        values.put(Telephony.TextBasedSmsColumns.ADDRESS, mDests[destIndex]);
        values.put(Telephony.TextBasedSmsColumns.DATE, mTimestamp);

        values.put(Telephony.TextBasedSmsColumns.READ, Integer.valueOf(1));
        values.put(Telephony.TextBasedSmsColumns.BODY, outboxText);

        // currently we do not support delivery reports
        values.put(Telephony.TextBasedSmsColumns.STATUS, Telephony.TextBasedSmsColumns.STATUS_NONE);

        if (mThreadId != -1L) {
            values.put(Telephony.TextBasedSmsColumns.THREAD_ID, mThreadId);
        }

        values.put(Inbox.TYPE, MessageItem.MESSAGE_TYPE_PARANDROID_OUTBOX);

        return mContext.getContentResolver().insert(Telephony.Sms.Outbox.CONTENT_URI, values);
    }

    /**
     * Get the service center to use for a reply.
     *
     * The rule from TS 23.040 D.6 is that we send reply messages to
     * the service center of the message to which we're replying, but
     * only if we haven't already replied to that message and only if
     * <code>TP-Reply-Path</code> was set in that message.
     *
     * Therefore, return the service center from the most recent
     * message in the conversation, but only if it is a message from
     * the other party, and only if <code>TP-Reply-Path</code> is set.
     * Otherwise, return null.
     */
    private String getOutgoingServiceCenter(long threadId) {
        Cursor cursor = null;

        try {
            cursor = SqliteWrapper.query(mContext, mContext.getContentResolver(), Sms.CONTENT_URI,
                    SERVICE_CENTER_PROJECTION, "thread_id = " + threadId, null, "date DESC");

            if ((cursor == null) || !cursor.moveToFirst()) {
                return null;
            }

            boolean replyPathPresent = (1 == cursor.getInt(COLUMN_REPLY_PATH_PRESENT));
            return replyPathPresent ? cursor.getString(COLUMN_SERVICE_CENTER) : null;
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }

    private void log(String msg) {
        Log.d(LogTag.TAG, "[SmsMsgSender] " + msg);
    }
}