Enviando e recebendo SMS e MMS no Android (pré Kit Kat Android 4.4)

131

Eu descobri como enviar e receber mensagens SMS. Para enviar mensagens SMS, tive que chamar os métodos sendTextMessage()e sendMultipartTextMessage()da SmsManagerclasse. Para receber mensagens SMS, tive que registrar um receptor no AndroidMainfest.xmlarquivo. Então eu tive que substituir o onReceive()método do BroadcastReceiver. Eu incluí exemplos abaixo.

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>

No entanto, eu queria saber se você poderia enviar e receber mensagens MMS de maneira semelhante. Depois de fazer algumas pesquisas, muitos exemplos fornecidos em blogs simplesmente passam um Intentpara o aplicativo de mensagens nativo. Estou tentando enviar um MMS sem sair do meu aplicativo. Não parece haver uma maneira padrão de enviar e receber MMS. Alguém conseguiu fazer isso funcionar?

Além disso, estou ciente de que o SMS / MMS ContentProvider não faz parte do SDK oficial do Android, mas estava pensando que alguém poderia implementá-lo. Qualquer ajuda é muito apreciada.

Atualizar

Adicionei um BroadcastReceiverao AndroidManifest.xmlarquivo para receber mensagens 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>

Na classe MMSReceiver, o onReceive()método só pode capturar o phoneNumber do qual a mensagem foi enviada. Como você pega outras coisas importantes de um MMS, como o caminho do arquivo para o anexo de mídia (imagem / áudio / vídeo) ou o texto no 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...
    }
}

De acordo com a documentação do android.provider.Telephony :

Ação de transmissão: Uma nova mensagem SMS baseada em texto foi recebida pelo dispositivo. A intenção terá os seguintes valores extras:

pdus- Um Object[]de byte[]s contendo as PDUs que compõem a mensagem.

Os valores extras podem ser extraídos usando getMessagesFromIntent(android.content.Intent) Se um BroadcastReceiver encontrar um erro ao processar essa intenção, ele deverá definir o código de resultado adequadamente.

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

Ação de transmissão: Uma nova mensagem SMS baseada em dados foi recebida pelo dispositivo. A intenção terá os seguintes valores extras:

pdus- Um Object[]de byte[]s contendo as PDUs que compõem a mensagem.

Os valores extras podem ser extraídos usando getMessagesFromIntent (android.content.Intent). Se um BroadcastReceiver encontrar um erro durante o processamento dessa intenção, ele deverá definir o código do resultado adequadamente.

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

Ação de transmissão: Uma nova mensagem WAP PUSH foi recebida pelo dispositivo. A intenção terá os seguintes valores extras:

transactionId (Integer) - O ID da transação WAP

pduType (Integer) - O tipo de WAP PDU`

header (byte[]) - O cabeçalho da mensagem

data (byte[]) - A carga útil de dados da mensagem

contentTypeParameters (HashMap<String,String>) - Quaisquer parâmetros associados ao tipo de conteúdo (decodificados no cabeçalho WSP Content-Type)

Se um BroadcastReceiver encontrar um erro durante o processamento dessa intenção, ele deverá definir o código do resultado adequadamente. O valor extra contentTypeParameters é um mapa dos parâmetros de conteúdo codificados por seus nomes. Se algum parâmetro conhecido não atribuído for encontrado, a chave do mapa será 'não atribuído / 0x ...', onde '...' é o valor hexadecimal do parâmetro não atribuído. Se um parâmetro tiver No-Value, o valor no mapa será nulo.

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

Atualização # 2

Eu descobri como passar extras em a PendingIntentpara serem recebidos por um BroadcastReceiver: Android PendingIntent extras, não recebidos por BroadcastReceiver

No entanto, o extra é passado para o SendBroadcastReceiver e não para o SMSReceiver . Como posso passar um extra para o SMSReceiver ?

Atualização # 3

Recebendo MMS

Então, depois de fazer mais pesquisas, vi algumas sugestões de registro de um ContentObserver. Dessa forma, você pode detectar quando há alguma alteração no content://mms-sms/conversationsProvedor de Conteúdo, consequentemente permitindo detectar MMS de entrada. Aqui está o exemplo mais próximo para que isso funcione, que eu encontrei: Recebendo MMS

No entanto, não é uma variável mainActivitydo tipo ServiceController. Onde a ServiceControllerclasse é implementada? Existem outras implementações de um registrado ContentObserver?

Enviando MMS

Quanto ao envio de MMS, me deparei com este exemplo: Enviar MMS

O problema é que tentei executar esse código no meu Nexus 4, que está no Android v4.2.2, e estou recebendo este erro:

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

O erro é gerado após consultar o CarriersContentProvider no getMMSApns()método da APNHelperclasse.

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

Aparentemente, você não pode ler APNs no Android 4.2

Qual é a alternativa para todos os aplicativos que usam dados móveis para realizar operações (como enviar MMS) e não conhecem a configuração padrão de APN presente no dispositivo?

Atualização # 4

Enviando MMS

Eu tentei seguir este exemplo: Enviar MMS

Como @Sam sugeriu em sua resposta:

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.

Então agora eu não recebo mais os erros SecurityException. Agora estou testando um Nexus 5 no Android KitKat. Depois de executar o código de amostra, ele me fornece um código de 200 respostas após a chamada para

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

No entanto, verifiquei com a pessoa para quem tentei enviar o MMS. E eles disseram que nunca receberam o MMS.

toobsco42
fonte
Você já deu uma olhada neste tutorial antes? Para obter mais
informações
3
Sim, eu tenho. Tentei reunir a resposta de Maxim, mas não consigo fazê-la funcionar. Existem muitas classes que importam android.provider.telephony que parece estar obsoleta.
precisa
E, presumivelmente, depois de ler a resposta de @ Sahil, você também tentou o seguinte: stackoverflow.com/questions/2972845/…
HaemEternal
Não sei ao certo como responder essa resposta, embora pareça muito semelhante à resposta de @ Sahil.
toobsco42
Oi @ toobsco42 você consegue encontrar a solução para todas as consultas mencionadas acima ..?
precisa saber é o seguinte

Respostas:

15

Eu tive exatamente o mesmo problema que você descreveu acima (Galaxy Nexus no t-mobile USA) é porque os dados móveis estão desativados.

No Jelly Bean, é: Configurações> Uso de Dados> dados móveis

Observe que eu preciso ativar os dados móveis antes de enviar um MMS OU receber um. Se eu receber um MMS com os dados móveis desativados, receberei a notificação de uma nova mensagem e a mensagem com o botão de download. Mas se eu não tiver dados móveis anteriormente, o anexo MMS recebido não será recebido. Mesmo se eu ligá-lo depois que a mensagem foi recebida.

Por algum motivo, quando o seu provedor de telefonia permitir que você envie e receba MMS, você deve ativar os Dados Móveis, mesmo se estiver usando Wifi, se os Dados Móveis estiverem ativados, você poderá receber e enviar MMS, mesmo que Wifi está aparecendo como sua internet no seu dispositivo.

É uma dor real, como se você não a estivesse ativada, a mensagem pode travar muito, mesmo ao ativar o Mobile Data, e pode exigir uma reinicialização do dispositivo.

Manan Sharma
fonte
Além disso, você deve saber que enviar SMS e MMS são duas coisas totalmente diferentes em segundo plano. O MMS é mais um serviço de rede baseado na Internet, pois requer o envio de itens adicionais (Mídia) com texto. O código fornecido funciona bem em alguns dispositivos em que testei. ps: você pode ignorar a parte NOKIA.
Manan Sharma
Quando executo este exemplo, no LogCat, ele imprime: 02-24 13: 32: 40.872: V / SendMMSActivity (5686): TYPE_MOBILE_MMS não conectado, retira 02-24 13: 32: 40.882: V / SendMMSActivity (5686): o tipo é não TYPE_MOBILE_MMS, fiança Também diz: java.lang.SecurityException: sem permissão para gravar configurações de APN: nem o usuário 10099 nem o processo atual têm android.permission.WRITE_APN_SETTINGS. Parece que não pode executar esta consulta: Cursor final apnCursor = this.context.getContentResolver (). Query (Uri.withAppendedPath (Carriers.CONTENT_URI, "current"), null, null, null, null); Estou testando em um Nexus 4.
toobsco42
Este também é o mesmo exemplo que o @Sahil forneceu.
toobsco42
7

Não há suporte oficial à API, o que significa que ele não está documentado para o público e as bibliotecas podem mudar a qualquer momento. Sei que você não deseja sair do aplicativo, mas veja como você o faz com a intenção de alguém se perguntar.

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"));

}

Ainda não descobri completamente como fazer coisas como rastrear a entrega da mensagem, mas isso deve ser enviado.

Você pode ser alertado para o recebimento de mms da mesma maneira que sms. O filtro de intenção no receptor deve ficar assim.

<intent-filter>
    <action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED" />
    <data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
user1959417
fonte
Isso não inicia o aplicativo nativo de Mensagens?
precisa saber é o seguinte
1
Sim, desculpe por isso. Acabei de perceber que você já sabia como fazer isso. eu adicionei como receber mms embora.
user1959417
Obrigado, recentemente implementei parte do MMS BroadcastReceivere usei o Intent Filterque você postou. Vou atualizar esta pergunta em breve.
precisa saber é o seguinte
4

Para enviar um mms para o Android 4.0 api 14 ou superior sem permissão para escrever configurações de apn, você pode usar esta biblioteca : Recupere códigos mnc e mcc do android e chame

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
    }
}

Para usar isso, adicione Jsoup e droid prism jar ao caminho da construção e importe com.droidprism. *;

Sam Adams
fonte
Hey @ Sam, adicionei o arquivo .jar ao meu projeto, mas estou recebendo esse erro na linha que instancia o objeto Carrier: java.lang.NoClassDefFoundError: com.droidprism.Carrier isso está acontecendo com você?
toobsco42
não. Você precisa adicionar jsoup ao caminho da compilação, o jar ao caminho da compilação e importar com.droidprism. *; Vou editar a resposta. Para fazer isso no android, adicione os jars ao diretório libs primeiro e, em seguida, configure o caminho de construção do projeto para usar os jars já existentes no diretório libs. Em seguida, na configuração do caminho de construção, clique em order e exporte, marque as caixas dos jars e mova jsoup e droidprism são colocados no topo da ordem de compilação.
23413 Sam Samshsh
Adicionar o Jsoup .jar resolveu o NoClassDefFoundError. Agora posso obter as configurações do APN. O próximo passo é descobrir como enviar MMS.
toobsco42
3

Eu não acho que existe algum suporte sdk para o envio de mms no android. Olhe aqui Pelo menos eu ainda não encontrei. Mas um cara alegou tê-lo. Dê uma olhada neste post.

Enviar MMS do meu aplicativo no android

Sahil Mahajan Mj
fonte
Analisei os comentários da postagem androidbridge.blogspot.com da implementação da Nokia e parece que muitas pessoas estão tendo problemas para que isso funcione em seus dispositivos.
precisa saber é o seguinte
@ toobsco42 Portanto, talvez ainda não haja suporte para isso.
Sahil Mahajan Mj
-2

Eu não entendo as frustrações. Por que não fazer um receptor de radiodifusão que filtre essa intenção:

android.provider.Telephony.MMS_RECEIVED

Verifiquei um pouco mais e talvez você precise de acesso no nível do sistema para obter este (telefone root).

j2emanue
fonte
3
Hey @ j2emanue, O problema é que depois que você recebe essa intenção, como você realmente obtém o conteúdo do MMS? Se um MMS contiver imagem e texto, como você extrai esses componentes.
toobsco42
mas noto que há uma matriz de bytes extra que você pode obter se fizer isso da maneira como mencionei .... byte [] data = intent.getByteArrayExtra ("data"); não tenho certeza de como analisá-lo, desculpe.
J2emanue
eu sou capaz de analisá-lo. mas tudo o que consigo entender é o assunto, de quem os mms vieram e o local do conteúdo em que o conteúdo dos mms está armazenado. no entanto, este URL não está acessível.
toobsco42
-2

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();

    }
}

Atividade

@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();
    }
}

Manifesto

<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>
Narendra Sorathiya
fonte