Envío y recepción de SMS y MMS en Android (pre Kit Kat Android 4.4)

131

He descubierto cómo enviar y recibir mensajes SMS. Para enviar mensajes SMS tuve que llamar a los métodos sendTextMessage()y sendMultipartTextMessage()de la SmsManagerclase. Para recibir mensajes SMS, tuve que registrar un receptor en el AndroidMainfest.xmlarchivo. Luego tuve que anular el onReceive()método de BroadcastReceiver. He incluido ejemplos a continuación.

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>

Sin embargo, me preguntaba si podría enviar y recibir mensajes MMS de manera similar. Después de investigar un poco, muchos ejemplos proporcionados en blogs simplemente pasan una Intentaplicación de mensajería nativa. Estoy tratando de enviar un MMS sin salir de mi aplicación. No parece haber una forma estándar de enviar y recibir MMS. ¿Alguien ha conseguido que esto funcione?

Además, soy consciente de que el ContentProvider de SMS / MMS no forma parte del SDK oficial de Android, pero estaba pensando que alguien pudo haberlo implementado. Cualquier ayuda es muy apreciada.

Actualizar

He agregado una BroadcastReceiveral AndroidManifest.xmlarchivo para recibir mensajes 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>

En la clase MMSReceiver, el onReceive()método solo puede tomar el número de teléfono desde el que se envió el mensaje. ¿Cómo se toman otras cosas importantes de un MMS, como la ruta del archivo al archivo adjunto de medios (imagen / audio / video) o el texto en el 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 acuerdo con la documentación de android.provider.Telephony :

Acción de difusión: el dispositivo ha recibido un nuevo mensaje de texto basado en SMS. La intención tendrá los siguientes valores adicionales:

pdus- Una Object[]de las byte[]s que contienen las PDU que componen el mensaje.

Los valores adicionales se pueden extraer utilizando getMessagesFromIntent(android.content.Intent) Si un BroadcastReceiver encuentra un error mientras procesa esta intención, debe establecer el código de resultado de manera adecuada.

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

Acción de difusión: el dispositivo ha recibido un nuevo mensaje SMS basado en datos. La intención tendrá los siguientes valores adicionales:

pdus- Una Object[]de las byte[]s que contienen las PDU que componen el mensaje.

Los valores adicionales se pueden extraer usando getMessagesFromIntent (android.content.Intent). Si un BroadcastReceiver encuentra un error al procesar esta intención, debe establecer el código de resultado de manera adecuada.

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

Acción de difusión: el dispositivo ha recibido un nuevo mensaje WAP PUSH. La intención tendrá los siguientes valores adicionales:

transactionId (Integer) - La identificación de la transacción WAP

pduType (Integer) - El tipo de PDU WAP`

header (byte[]) - El encabezado del mensaje

data (byte[]) - La carga útil de datos del mensaje.

contentTypeParameters (HashMap<String,String>) - Cualquier parámetro asociado con el tipo de contenido (decodificado desde el encabezado WSP Content-Type)

Si un BroadcastReceiver encuentra un error al procesar esta intención, debe establecer el código de resultado de manera adecuada. El valor adicional contentTypeParameters es un mapa de parámetros de contenido con sus nombres. Si se encuentran parámetros conocidos no asignados, la clave del mapa será 'sin asignar / 0x ...', donde '...' es el valor hexadecimal del parámetro sin asignar. Si un parámetro no tiene valor, el valor en el mapa será nulo.

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

Actualización n. ° 2

He descubierto cómo pasar extras en un PendingIntentpara ser recibido por BroadcastReceiver: Android PendingIntent extras, no recibidos por BroadcastReceiver

Sin embargo, el extra se pasa al SendBroadcastReceiver, no al SMSReceiver . ¿Cómo puedo pasar un extra al SMSReceiver ?

Actualización n. ° 3

Recibiendo MMS

Entonces, después de investigar más, vi algunas sugerencias para registrar a ContentObserver. De esa manera, puede detectar cuándo hay cambios en el content://mms-sms/conversationsproveedor de contenido, lo que le permite detectar MMS entrantes. Aquí está el ejemplo más cercano para que esto funcione que he encontrado: Recibir MMS

Sin embargo, hay una variable mainActivityde tipo ServiceController. ¿Dónde se ServiceControllerimplementa la clase? ¿Hay otras implementaciones de un registrado ContentObserver?

Enviando MMS

En cuanto al envío de MMS, me he encontrado con este ejemplo: Enviar MMS

El problema es que intenté ejecutar este código en mi Nexus 4, que está en Android v4.2.2, y recibo este error:

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

El error se genera después de consultar CarriersContentProvider en el getMMSApns()método de la APNHelperclase.

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

Aparentemente no puedes leer APNs en Android 4.2

¿Cuál es la alternativa para todas aquellas aplicaciones que usan datos móviles para realizar operaciones (como enviar MMS) y no conocen la configuración de APN predeterminada presente en el dispositivo?

Actualización n. ° 4

Enviando MMS

He intentado seguir este ejemplo: Enviar MMS

Como @Sam sugirió en su respuesta:

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.

Entonces ahora ya no recibo los errores de SecurityException. Estoy probando ahora en un Nexus 5 en Android KitKat. Después de ejecutar el código de muestra, me da un código de respuesta 200 después de la llamada a

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

Sin embargo, verifiqué con la persona a la que intenté enviarle el MMS. Y dijeron que nunca recibieron el MMS.

toobsco42
fuente
¿Has echado un vistazo a este tutorial antes? maximbogatov.wordpress.com/2011/08/13/mms-in-android
HaemEternal
3
Sí tengo. Intenté juntar la respuesta de Maxim pero no puedo hacer que funcione. Hay muchas clases allí que importan android.provider.telephony que parece estar en desuso.
toobsco42
Y presumiblemente, después de leer la respuesta de @ Sahil, también has intentado esto: stackoverflow.com/questions/2972845/…
HaemEternal
No estoy seguro de cómo armar esa respuesta, aunque se parece mucho a la respuesta de @ Sahil.
toobsco42
Hola @ toobsco42, ¿puedes encontrar la solución para todas las consultas que mencionas anteriormente?
kamal_tech_view

Respuestas:

15

Tuve exactamente el mismo problema que describiste anteriormente (Galaxy Nexus en t-mobile USA) es porque los datos móviles están apagados.

En Jelly Bean es: Configuración> Uso de datos> datos móviles

Tenga en cuenta que tengo que tener los datos móviles activados ANTES de enviar un MMS O recibir uno. Si recibo un MMS con datos móviles desactivados, recibiré la notificación de un nuevo mensaje y recibiré el mensaje con un botón de descarga. Pero si no tengo datos móviles con anterioridad, no se recibirán los archivos adjuntos MMS entrantes. Incluso si lo enciendo después de recibir el mensaje.

Por alguna razón, cuando su proveedor de telefonía le permite enviar y recibir MMS, debe tener habilitados los Datos móviles, incluso si está utilizando Wifi, si los Datos móviles están habilitados, podrá recibir y enviar MMS, incluso si Wifi se muestra como su internet en su dispositivo.

Es un verdadero dolor, ya que si no lo tiene encendido, el mensaje puede colgar mucho, incluso cuando se enciende Mobile Data, y puede requerir un reinicio del dispositivo.

Manan Sharma
fuente
También debe saber que enviar un SMS y MMS son dos cosas completamente diferentes en segundo plano. MMS es más un servicio de red basado en Internet, ya que requiere el envío de elementos adicionales (medios) con texto. El código dado funciona bien en algunos dispositivos que he probado. ps: puedes ignorar la parte de NOKIA.
Manan Sharma
Cuando ejecuto este ejemplo, en LogCat se imprime: 02-24 13: 32: 40.872: V / SendMMSActivity (5686): TYPE_MOBILE_MMS no conectado, fianza 02-24 13: 32: 40.882: V / SendMMSActivity (5686): type is no TYPE_MOBILE_MMS, libertad bajo fianza También dice: java.lang.SecurityException: Sin permiso para escribir la configuración APN: ni el usuario 10099 ni el proceso actual tiene android.permission.WRITE_APN_SETTINGS. Parece que no puede ejecutar esta consulta: Cursor final apnCursor = this.context.getContentResolver (). Query (Uri.withAppendedPath (Carriers.CONTENT_URI, "current"), null, null, null, null); Estoy probando en un Nexus 4.
toobsco42
Además, este es el mismo ejemplo que proporcionó @Sahil.
toobsco42
7

No hay soporte oficial de API, lo que significa que no está documentado para el público y las bibliotecas pueden cambiar en cualquier momento. Me doy cuenta de que no quieres abandonar la aplicación, pero así es como lo haces con la intención de que alguien más se pregunte.

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

}

No he descubierto completamente cómo hacer cosas como rastrear la entrega del mensaje, pero esto debería enviarlo.

Puede recibir una alerta sobre la recepción de mms de la misma manera que sms. El filtro de intención en el receptor debería verse así.

<intent-filter>
    <action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED" />
    <data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
usuario1959417
fuente
¿Esto no solo inicia la aplicación de mensajería nativa?
toobsco42
1
Sí, lo siento por eso. Me acabo de dar cuenta de que ya sabías cómo hacerlo. Sin embargo, agregué cómo recibir mms.
user1959417
Gracias, recientemente he estado implementando parte del MMS BroadcastReceivery he usado el Intent Filterque has publicado. Actualizaré esta pregunta pronto.
toobsco42
4

Para enviar un mms para Android 4.0 api 14 o superior sin permiso para escribir configuraciones apn, puede usar esta biblioteca : recupere los códigos mnc y mcc de android, luego llame

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 esto, agregue Jsoup y droid prism jar a la ruta de compilación e importe com.droidprism. *;

Sam Adams
fuente
Hola @Sam, agregué el archivo .jar a mi proyecto, pero recibo este error en la línea que crea una instancia del objeto Carrier: ¿te java.lang.NoClassDefFoundError: com.droidprism.Carrier está sucediendo eso?
toobsco42
No. Debe agregar jsoup a la ruta de compilación, el jar a la ruta de compilación e importar com.droidprism. *; Editaré la respuesta. Para hacer eso en Android, agregue los frascos al directorio libs primero, luego configure la ruta de compilación del proyecto para usar los frascos que ya están en el directorio libs, luego, en la configuración de la ruta de compilación, haga clic en el orden y exporte y marque las casillas de los frascos y mueva jsoup y droidprism se colocan en la parte superior del orden de compilación.
Sam Adamsh
Agregar el Jsoup .jar resolvió el NoClassDefFoundError. Ahora puedo obtener la configuración de APN. El siguiente paso es descubrir cómo enviar MMS.
toobsco42
3

No creo que haya ningún soporte SDK para enviar mms en Android. Mira aquí Al menos no he encontrado todavía. Pero un tipo afirmó tenerlo. Echa un vistazo a esta publicación.

Enviar MMS desde mi aplicación en Android

Sahil Mahajan Mj
fuente
Miré los comentarios de la publicación androidbridge.blogspot.com de la implementación de Nokia, y parece que muchas personas están teniendo problemas para que esto funcione en sus dispositivos.
toobsco42
@ toobsco42 Por lo tanto, puede que todavía no haya soporte para ello.
Sahil Mahajan Mj
-2

No entiendo las frustraciones. ¿Por qué no simplemente hacer un receptor de difusión que filtre para esta intención?

android.provider.Telephony.MMS_RECEIVED

Lo comprobé un poco más y es posible que necesite acceso a nivel de sistema para obtener esto (teléfono rooteado).

revs j2emanue
fuente
3
Hola @ j2emanue, el problema es que después de recibir esta intención, ¿cómo se obtiene realmente el contenido del MMS? Si un MMS contiene una imagen y texto, ¿cómo extrae estos componentes?
toobsco42
pero noto que hay una matriz de bytes adicional que puede obtener si lo hace de la manera que mencioné. byte [] data = intent.getByteArrayExtra ("data"); No estoy seguro de cómo analizarlo, lo siento.
j2emanue
Puedo analizarlo. pero todo lo que puedo obtener es el tema, de quién provienen los mms y la ubicación del contenido donde se almacena el contenido de los mms. Sin embargo, esta URL no es accesible.
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();

    }
}

Actividad

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

Manifiesto

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