Отправка и получение SMS и MMS на Android (pre Kit Kat Android 4.4)

Я выяснил, как отправлять и получать SMS-сообщения. Для отправки SMS-сообщений мне пришлось вызвать методы sendTextMessage() и sendMultipartTextMessage() класса SmsManager. Чтобы получать SMS-сообщения, мне пришлось регистрировать приемник в файле AndroidMainfest.xml. Затем мне пришлось переопределить метод onReceive() BroadcastReceiver. Я привел примеры ниже.

MainActivity.java

public class MainActivity extends Activity {
    private static String SENT = "SMS_SENT";
    private static String DELIVERED = "SMS_DELIVERED";
    private static int MAX_SMS_MESSAGE_LENGTH = 160;

    // ---sends an SMS message to another device---
    public static void sendSMS(String phoneNumber, String message) {

        PendingIntent piSent = PendingIntent.getBroadcast(mContext, 0, new Intent(SENT), 0);
        PendingIntent piDelivered = PendingIntent.getBroadcast(mContext, 0,new Intent(DELIVERED), 0);
        SmsManager smsManager = SmsManager.getDefault();

        int length = message.length();          
        if(length > MAX_SMS_MESSAGE_LENGTH) {
            ArrayList<String> messagelist = smsManager.divideMessage(message);          
            smsManager.sendMultipartTextMessage(phoneNumber, null, messagelist, null, null);
        }
        else
            smsManager.sendTextMessage(phoneNumber, null, message, piSent, piDelivered);
        }
    }

    //More methods of MainActivity ...
}

SMSReceiver.java

public class SMSReceiver extends BroadcastReceiver {
    private final String DEBUG_TAG = getClass().getSimpleName().toString();
    private static final String ACTION_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
    private Context mContext;
    private Intent mIntent;

    // Retrieve SMS
    public void onReceive(Context context, Intent intent) {
        mContext = context;
        mIntent = intent;

        String action = intent.getAction();

        if(action.equals(ACTION_SMS_RECEIVED)){

            String address, str = "";
            int contactId = -1;

            SmsMessage[] msgs = getMessagesFromIntent(mIntent);
            if (msgs != null) {
                for (int i = 0; i < msgs.length; i++) {
                    address = msgs[i].getOriginatingAddress();
                    contactId = ContactsUtils.getContactId(mContext, address, "address");
                    str += msgs[i].getMessageBody().toString();
                    str += "\n";
                }
            }   

            if(contactId != -1){
                showNotification(contactId, str);
            }

            // ---send a broadcast intent to update the SMS received in the
            // activity---
            Intent broadcastIntent = new Intent();
            broadcastIntent.setAction("SMS_RECEIVED_ACTION");
            broadcastIntent.putExtra("sms", str);
            context.sendBroadcast(broadcastIntent);
        }

    }

    public static SmsMessage[] getMessagesFromIntent(Intent intent) {
        Object[] messages = (Object[]) intent.getSerializableExtra("pdus");
        byte[][] pduObjs = new byte[messages.length][];

        for (int i = 0; i < messages.length; i++) {
            pduObjs[i] = (byte[]) messages[i];
        }
        byte[][] pdus = new byte[pduObjs.length][];
        int pduCount = pdus.length;
        SmsMessage[] msgs = new SmsMessage[pduCount];
        for (int i = 0; i < pduCount; i++) {
            pdus[i] = pduObjs[i];
            msgs[i] = SmsMessage.createFromPdu(pdus[i]);
        }
        return msgs;
    }

    /**
    * The notification is the icon and associated expanded entry in the status
    * bar.
    */
    protected void showNotification(int contactId, String message) {
        //Display notification...
    }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.myexample"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="16"
        android:targetSdkVersion="17" />

    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />
    <uses-permission android:name="android.permission.WRITE_SMS" />
    <uses-permission android:name="android.permission.RECEIVE_MMS" />
    <uses-permission android:name="android.permission.WRITE" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:debuggable="true"
        android:icon="@drawable/ic_launcher_icon"
        android:label="@string/app_name" >

        <activity
            //Main activity...
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            //Activity 2 ...
        </activity>
        //More acitivies ...

        // SMS Receiver
        <receiver android:name="com.myexample.receivers.SMSReceiver" >
            <intent-filter>
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver>

    </application>
</manifest>

Однако мне было интересно, можно ли отправлять и получать MMS-сообщения аналогичным образом. После нескольких исследований многие примеры, представленные в блогах, просто передают Intent в собственное приложение Messaging. Я пытаюсь отправить MMS без моего заявления. Кажется, что нет стандартного способа отправки и получения MMS. Кто-нибудь получил это, чтобы работать?

Кроме того, я знаю, что SMS/MMS ContentProvider не является частью официального Android SDK, но я думал, что кто-то, возможно, смог это реализовать. Любая помощь приветствуется.

Update

Я добавил файл BroadcastReceiver в файл AndroidManifest.xml для приема MMS-сообщений

<receiver android:name="com.sendit.receivers.MMSReceiver" >
    <intent-filter>
        <action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED" />

        <data android:mimeType="application/vnd.wap.mms-message" />
    </intent-filter>
</receiver>

В классе MMSReceiver метод onReceive() способен только захватить номер телефона, с которого было отправлено сообщение. Как вы берете другие важные вещи из MMS, такие как путь к медиа-вложению (изображение/аудио/видео) или текст в MMS?

MMSReceiver.java

public class MMSReceiver extends BroadcastReceiver {
    private final String DEBUG_TAG = getClass().getSimpleName().toString();
    private static final String ACTION_MMS_RECEIVED = "android.provider.Telephony.WAP_PUSH_RECEIVED";
    private static final String MMS_DATA_TYPE = "application/vnd.wap.mms-message";

     // Retrieve MMS
    public void onReceive(Context context, Intent intent) {

        String action = intent.getAction();
        String type = intent.getType();

        if(action.equals(ACTION_MMS_RECEIVED) && type.equals(MMS_DATA_TYPE)){

            Bundle bundle = intent.getExtras();

            Log.d(DEBUG_TAG, "bundle " + bundle);
            SmsMessage[] msgs = null;
            String str = "";
            int contactId = -1;
            String address;

            if (bundle != null) {

                byte[] buffer = bundle.getByteArray("data");
                Log.d(DEBUG_TAG, "buffer " + buffer);
                String incomingNumber = new String(buffer);
                int indx = incomingNumber.indexOf("/TYPE");
                if(indx>0 && (indx-15)>0){
                    int newIndx = indx - 15;
                    incomingNumber = incomingNumber.substring(newIndx, indx);
                    indx = incomingNumber.indexOf("+");
                    if(indx>0){
                        incomingNumber = incomingNumber.substring(indx);
                        Log.d(DEBUG_TAG, "Mobile Number: " + incomingNumber);
                    }
                }

                int transactionId = bundle.getInt("transactionId");
                Log.d(DEBUG_TAG, "transactionId " + transactionId);

                int pduType = bundle.getInt("pduType");
                Log.d(DEBUG_TAG, "pduType " + pduType);

                byte[] buffer2 = bundle.getByteArray("header");      
                String header = new String(buffer2);
                Log.d(DEBUG_TAG, "header " + header);

                if(contactId != -1){
                    showNotification(contactId, str);
                }

                // ---send a broadcast intent to update the MMS received in the
                // activity---
                Intent broadcastIntent = new Intent();
                broadcastIntent.setAction("MMS_RECEIVED_ACTION");
                broadcastIntent.putExtra("mms", str);
                context.sendBroadcast(broadcastIntent);

            }
        }

    }

    /**
    * The notification is the icon and associated expanded entry in the status
    * bar.
    */
    protected void showNotification(int contactId, String message) {
        //Display notification...
    }
}

В соответствии с Документация android.provider.Telephony:

Трансляция: новое SMS-сообщение на основе текстовой информации было получено устройством. У намерения будут следующие дополнительные значения:

pdus - Object[] of byte[], содержащий PDU, которые составляют сообщение.

Дополнительные значения можно извлечь, используя getMessagesFromIntent(android.content.Intent)Если BroadcastReceiver сталкивается с ошибкой при обработке этого намерения, он должен правильно установить код результата.

 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
 public static final String SMS_RECEIVED_ACTION = "android.provider.Telephony.SMS_RECEIVED";

Трансляция: новое устройство на основе данных передало SMS-сообщение. У намерения будут следующие дополнительные значения:

pdus - Object[] of byte[], содержащий PDU, которые составляют сообщение.

Дополнительные значения можно извлечь, используя getMessagesFromIntent (android.content.Intent). Если BroadcastReceiver сталкивается с ошибкой при обработке этого намерения, он должен правильно установить код результата.

@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String DATA_SMS_RECEIVED_ACTION = "android.intent.action.DATA_SMS_RECEIVED";

Трансляция: новое устройство WAP PUSH было получено устройством. У намерения будут следующие дополнительные значения:

transactionId (Integer) - Идентификатор транзакции WAP

pduType (Integer) - Тип WAP PDU

header (byte[]) - заголовок сообщения

data (byte[]) - Полезная нагрузка данных сообщения

contentTypeParameters (HashMap<String,String>) - Любые параметры, связанные с типом контента (декодированные из заголовка Content-Type WSP)

Если BroadcastReceiver обнаруживает ошибку при обработке этого намерения, он должен соответствующим образом установить код результата. Дополнительное значение contentTypeParameters - это карта параметров контента, подкрепленных их именами. Если обнаружены какие-либо непризнанные хорошо известные параметры, ключ карты будет "неназначен/0x...", где "..." является шестнадцатеричным значением неназначенного параметра. Если параметр имеет значение No-value, значение на карте будет равно null.

@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String WAP_PUSH_RECEIVED_ACTION = "android.provider.Telephony.WAP_PUSH_RECEIVED";

Обновление # 2

Я выяснил, как передать дополнительные функции в PendingIntent, которые должны быть получены BroadcastReceiver: Дополнительные функции Android PendingIntent, не полученные BroadcastReceiver

Однако, дополнительная передача передается в SendBroadcastReceiver, а не в SMSReceiver. Как передать дополнительную информацию в SMSReceiver?

Обновление # 3

Получение MMS

Итак, после проведения дополнительных исследований я увидел несколько предложений о регистрации ContentObserver. Таким образом, вы можете обнаружить, когда есть какие-либо изменения в контент-провайдере content://mms-sms/conversations, что позволяет обнаруживать входящие MMS. Вот ближайший пример, чтобы заставить это работать, что я нашел: Получение MMS

Однако существует переменная mainActivity типа ServiceController. Где реализуется класс ServiceController? Существуют ли какие-либо другие реализации зарегистрированного ContentObserver?

Отправка MMS

Что касается отправки MMS, я столкнулся с этим примером: Отправить MMS

Проблема в том, что я попытался запустить этот код на своем Nexus 4, который находится на Android v4.2.2, и я получаю эту ошибку:

java.lang.SecurityException: No permission to write APN settings: Neither user 10099 nor current process has android.permission.WRITE_APN_SETTINGS.

Ошибка возникает после запроса Carriers ContentProvider в методе getMMSApns() класса APNHelper.

final Cursor apnCursor = this.context.getContentResolver().query(Uri.withAppendedPath(Carriers.CONTENT_URI, "current"), null, null, null, null);

По-видимому, вы не можете читать APN в Android 4.2

Какова альтернатива для всех тех приложений, которые используют мобильные данные для выполнения операций (например, отправки MMS), и не знают настройки APN по умолчанию, присутствующие в устройстве?

Обновление # 4

Отправка MMS

Я попытался выполнить следующий пример: Отправить MMS

Как @Sam предложил в своем ответе:

You have to add jsoup to the build path, the jar to the build path and import com.droidprism.*; To do that in android, add the jars to the libs directory first, then configure the project build path to use the jars already in the libs directory, then on the build path config click order and export and check the boxes of the jars and move jsoup and droidprism jar to the top of the build order.

Итак, теперь я больше не получаю ошибки SecurityException. Сейчас я тестирую Nexus 5 на Android KitKat. После запуска кода примера он дает мне код ответа 200 после вызова

MMResponse mmResponse = sender.send(out, isProxySet, MMSProxy, MMSPort);

Однако я проверил с человеком, которого я пытался отправить MMS. И они сказали, что они никогда не получали MMS.

Ответ 1

У меня была такая же проблема, которую вы описали выше (Galaxy Nexus на t-mobile USA), потому что мобильные данные отключены.

В желе Bean это: Настройки > Использование данных > мобильные данные

Обратите внимание, что мне нужно, чтобы мобильные данные были включены в PRIOR для отправки MMS или получения. Если я получаю MMS с отключенными мобильными данными, я получаю уведомление о новом сообщении, и я получаю сообщение с кнопкой загрузки. Но если у меня нет мобильных данных ранее, входящее MMS-приложение не будет получено. Даже если я включу его после получения сообщения.

По какой-либо причине, когда ваш оператор телефона позволяет вам отправлять и получать MMS, вы должны включить Mobile Data, даже если вы используете Wi-Fi, если мобильные данные включены, вы сможете получать и отправлять MMS, даже если Wi-Fi отображается как ваш интернет на вашем устройстве.

Это настоящая боль, как будто у вас ее нет, сообщение может сильно зависать даже при включении мобильных данных и может потребовать перезагрузки устройства.

Ответ 2

Существует официальная поддержка api, которая означает, что она не документирована для общественности, и библиотеки могут меняться в любое время. Я понимаю, что вы не хотите оставлять заявку, но здесь, как вы это делаете, с намерением кого-то еще задаться вопросом.

public void sendData(int num){
    String fileString = "..."; //put the location of the file here
    Intent mmsIntent = new Intent(Intent.ACTION_SEND);
    mmsIntent.putExtra("sms_body", "text");
    mmsIntent.putExtra("address", num);
    mmsIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(fileString)));
    mmsIntent.setType("image/jpeg");
    startActivity(Intent.createChooser(mmsIntent, "Send"));

}

Я не совсем понял, как делать такие вещи, как отслеживать доставку сообщения, но это должно заставить его отправить.

Вы можете получить предупреждение о получении mms так же, как смс. Фильтр намерений на приемнике должен выглядеть следующим образом.

<intent-filter>
    <action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED" />
    <data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>

Ответ 3

Чтобы отправить mms для Android 4.0 api 14 или выше без разрешения на запись настроек apn, вы можете использовать эту библиотеку: Извлеките mnc и mcc коды из android, затем вызовите

Carrier c = Carrier.getCarrier(mcc, mnc);
if (c != null) {
    APN a = c.getAPN();
    if (a != null) {
        String mmsc = a.mmsc;
        String mmsproxy = a.proxy; //"" if none
        int mmsport = a.port; //0 if none
    }
}

Чтобы использовать это, добавьте Jsoup и банку призмы дроидов в путь сборки и импортируйте com.droidprism. *;

Ответ 5

Я не понимаю разочарования. Почему бы просто не сделать трансляцию, которая фильтрует для этого намерения:

android.provider.Telephony.MMS_RECEIVED

Я проверил немного дальше, и вам может понадобиться доступ к системному уровню для получения этого (корневого телефона).

Ответ 6

SmsListenerClass

public class SmsListener extends BroadcastReceiver {

static final String ACTION =
        "android.provider.Telephony.SMS_RECEIVED";

@Override
public void onReceive(Context context, Intent intent) {

    Log.e("RECEIVED", ":-:-" + "SMS_ARRIVED");

    // TODO Auto-generated method stub
    if (intent.getAction().equals(ACTION)) {

        Log.e("RECEIVED", ":-" + "SMS_ARRIVED");

        StringBuilder buf = new StringBuilder();
        Bundle bundle = intent.getExtras();
        if (bundle != null) {

            Object[] pdus = (Object[]) bundle.get("pdus");

            SmsMessage[] messages = new SmsMessage[pdus.length];
            SmsMessage message = null;

            for (int i = 0; i < messages.length; i++) {

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    String format = bundle.getString("format");
                    messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i], format);
                } else {
                    messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
                }

                message = messages[i];
                buf.append("Received SMS from  ");
                buf.append(message.getDisplayOriginatingAddress());
                buf.append(" - ");
                buf.append(message.getDisplayMessageBody());
            }

            MainActivity inst = MainActivity.instance();
            inst.updateList(message.getDisplayOriginatingAddress(),message.getDisplayMessageBody());

        }

        Log.e("RECEIVED:", ":" + buf.toString());

        Toast.makeText(context, "RECEIVED SMS FROM :" + buf.toString(), Toast.LENGTH_LONG).show();

    }
}

Деятельность

@Override
public void onStart() {
    super.onStart();
    inst = this;
}

public static MainActivity instance() {
    return inst;
}

public void updateList(final String msg_from, String msg_body) {

    tvMessage.setText(msg_from + " :- " + msg_body);

    sendSMSMessage(msg_from, msg_body);

}

protected void sendSMSMessage(String phoneNo, String message) {

    try {
        SmsManager smsManager = SmsManager.getDefault();
        smsManager.sendTextMessage(phoneNo, null, message, null, null);
        Toast.makeText(getApplicationContext(), "SMS sent.", Toast.LENGTH_LONG).show();
    } catch (Exception e) {
        Toast.makeText(getApplicationContext(), "SMS faild, please try again.", Toast.LENGTH_LONG).show();
        e.printStackTrace();
    }
}

манифеста

<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.SEND_SMS"/>

<receiver android:name=".SmsListener">
        <intent-filter>
            <action android:name="android.provider.Telephony.SMS_RECEIVED" />
        </intent-filter>
    </receiver>