Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Spedire SMS in un'app Android

Sviluppare un'applicazione per Android per spedire SMS. La gestione degli Intent e dell'SMSManager
Sviluppare un'applicazione per Android per spedire SMS. La gestione degli Intent e dell'SMSManager
Link copiato negli appunti

Gli SMS sono la data application più utilizzata al mondo. Per chi si occupa di sviluppo software su piattaforme mobile è essenziale saper utilizzare i servizi a disposizione per la gestione degli SMS: questo articolo ha lo scopo di introdurre i concetti e i meccanismi fondamentali con i quali possiamo gestire gli SMS all'interno delle nostre applicazioni Android.

Lo scopo di queste lezioni è quello di approfondire la gestione degli SMS in Android: come nell’articolo sulla gestione del database, tutto il codice e gli esempi che vedremo saranno specifici e incentrati su questo determinato argomento. Rimandiamo alla guida allo sviluppo di app per Android per le argomentazioni introduttive, come la configurazione dell'ambiente di sviluppo e la creazione di un progetto.

Come vedremo più avanti in questo articolo, gli strumenti che abbiamo a disposizione permettono di tracciare in maniera completa e puntuale l'intero ciclo di vita dell'SMS: questo ci fornisce la possibilità di controllare quanto sta avvenendo ed assegnare così un comportamento alla nostra app coerente con la situazione (ad esempio possiamo fornire un feedback preciso all'utente sull'effettivo inoltro e ricezione dell'SMS da parte del destinatario).

Testare la nostra app con l’emulatore

Rimandando alla lezione specifica della nostra guida l’approfondimento dei software da utilizzare per lo sviluppo, soffermiamoci per un istante sull’emulatore dell’SDK. Quando trattiamo la gestione degli SMS l'emulatore diventa un utile strumento per testare il comportamento della nostra applicazione sia per quanto riguarda gli SMS inviati sia per quanto riguarda quelli in ricezione.

Prima di approfondire il discorso tecnico vediamo dunque come sfruttare l'emulatore per simulare l'invio e la ricezione di un SMS.

Per simulare l'invio di SMS abbiamo chiaramente bisogno di almeno 2 emulatori, uno per inviare l'SMS (emulatore A) ed uno per riceverlo (emulatore B): è l'Android Debugging Bridge (adb) che supporta l'invio di messaggi SMS tra istanze multiple di emulatori. L'adb è un potente tool che si utilizza da linea di comando e che permette di comunicare sia con gli emulatori sia con il nostro device Android (purché connesso al computer).

L’importanza dell’Android Debugging Bridge

L'adb è un programma client-server composto da 3 elementi:

  1. un client che gira sulla nostra macchina di sviluppo;
  2. un server che gira come processo in background sulla nostra macchina di sviluppo: il server si occupa di gestire la comunicazione tra il client e il daemon adb (vedi punto 3 subito sotto) che a sua volta gira sull'emulatore o sul device;
  3. un daemon che gira come processo in background su ogni emulatore o sull'istanza del device;

L'adb è un tool che viene rilasciato insieme all'SDK (Software Development Kit) di Android, e lo possiamo trovare sotto la directory <sdk>/platform-tools/. Per chi volesse approfondire la conoscenza e lo studio dell'Android Debug Bridge è disponibile sul sito ufficiale degli sviluppatori Android una pagina di supporto nella quale possiamo approfondire il funzionamento del tool e studiarne i comandi principali a disposizione. Consiglio vivamente di dare almeno un'occhiata veloce, perchè l'adb è uno strumento davvero potente che spesso viene sottovaluto o ignorato.

Giusto per avere un'idea dell'importanza di questo tool e delle funzionalità che ci mette a disposizione possiamo riassumere brevemente alcune tra le caratteristiche più interessanti:

  1. permette di effettuare query su un specifico emulatore o device;
  2. permette di esaminare da shell il database della nostra applicazione sia se installata sull'emulatore sia se installata direttamente sul device;
  3. fermare il server dell'adb (in alcune circostanze diventa necessario killare il server adb ad esempio perché non risponde o perché non è stato avviato con i permessi corretti e dunque l'istanza non lavora come dovrebbe);

Simulare l’invio degli SMS

Ma torniamo alla nostra app. Supponiamo dunque di voler spedire un messaggio SMS dall'emulatore A all'emulatore B: tutto ciò che dobbiamo fare è specificare il numero di porta dell'emulatore di destinazione, proprio come facciamo compilando il campo “destinatario” quando inviamo un nuovo messaggio SMS. Android si occuperà di inoltrare automaticamente il nostro messaggio all'istanza di destinazione dell'emulatore, messaggio che a questo punto sarà gestito come un normale SMS.

Il numero di porta, supponendo che all'emulatore A (sender) e all'emulatore B (receiver) siano state assegnate rispettivamente le porte 5556 e 5554, lo troviamo nel titolo della finestra in cui viene visualizzato l'emulatore. La figura 1.1 mostra l'emulatore B, il destinatario dell'SMS:

Emulatore SMS Android

Figura 1.1: il titolo dell'emulatore con la rispettiva porta di comunicazione

Come mostrato in figura, nell'angolo in alto a sinistra possiamo vedere la porta in cui sta “girando” l'emulatore (5554), pertanto sarà sufficiente trattare il numero di porta “5554” come se fosse il numero di telefono del destinarlo.

Android fornisce principalmente due metodi per inviare SMS da una nostra applicazione: la prima metodologia sfrutta gli Intents mentre la seconda si affida alle funzionalità dell'SMSManager.

Invio degli SMS con gli Intent e con il client nativo

Gli Intent sono descrizioni astratte di operazione che possono essere svolte (si dia un’occhiata alla reference). Tecnicamente gli Intents sono messaggi asincroni che permettono ai componenti Android di richiamare e richiedere funzionalità gestite da altri componenti Android.

L'esempio più comune di utilizzo è quello che permette ad una Activity di richiedere al sistema Android di far partire un'altra Activity: questa è una tipologia di chiamata che eseguiamo praticamente dal primo progetto Android che sviluppiamo, pertanto gli Intent sono così comodi che in alcuni casi non ci accorgiamo nemmeno di usarli.

Gli Intent sono quindi una componente molto importante della piattaforma Android, perché permettono di combinare insieme diverse operazioni in modo molto trasparente, faciltando notelvolmente lo svolgere di alcuni processi.

Gli Intent possono anche essere utilizzati per segnalare al sistema Android l'accadere di determinati eventi, ed altri componenti possono essere “registrati” a tali eventi per raccogliere particolari notifiche e svolgere così un compito specifico.

La potenza degli Intent non si ferma qui. Questi infatti possono anche contenere informazioni e dati che possono essere condivisi e utilizzati dal componente ricevente: ad esempio la nostra applicazione può richiamare attraverso un Intent il componente browser passandogli un URI (Universal Resource Identifiers) da aprire.

La piattaforma Android mette a disposizione due tipologie diverse di Intent, quelli espliciti e quelli impliciti. Li vedremo nella seconda parte di questo articolo in pubblicazione la prossima settimana.

Intent espliciti

Gli Intent espliciti specificano il preciso componente che deve essere chiamato dalla piattaforma Android utilizzando come identificatore la classe Java.

Come specificato poc'anzi, uno degli Intent che utilizzeremo di più durante lo sviluppo delle nostre applicazioni Android è quello che permette ad una Activity di "chiamare e far partire" una seconda Activity. Questo è un buon esempio di Intent esplicito, il cui codice potrebbe essere simile al seguente:

Intent intent = new Intent(ActivityA.this, ActivityB.class);
startActivity(intent);

codice 1.1

Come mostrato nell'esempio, il codice è molto semplice e praticamente autoesplicativo: creiamo un oggetto Intent passandogli come primo parametro una reference alla Activity chiamante (ActivityA) e come secondo parametro una reference esplicita all'Activity da richiamare (ActivityB). È poi sufficiente richiamare il metodo startActivity() passandogli come parametro l' Intent appena creato per far partire l'Activity da eseguire (nel nostro esempio l'Activity B) e chiudere così il cerchio.

Una caratteristica importante degli Intent è la facilità con la quale permettono il passaggio di parametri e la condivisione di dati ed informazioni. Grazie al metodo "putExtra()" diventa davvero un gioco da ragazzi passare uno o più parametri dall'ActivityA all' ActivityB:

Intent intent = new Intent(ActivityA.this, ActivityB.class);
intent.putExtra("nomeValore1", "Valore");
intent.putExtra("nomeValore2", "Valore");
startActivity(intent);

codice 1.2

Come mostrato nel codice 1.1 e 1.2, grazie ai meccanismi degli Intent possiamo facilmente richiamare Activity condividendo informazioni: vedremo tra poco come sfruttare questi importanti strumenti per inviare SMS.

Intent impliciti

Gli Intent impliciti non specificano direttamente quali componenti Android devono essere richiamati per svolgere un determinato compito: essi specificano esclusivamente l'azione che deve essere svolta e opzionalmente un URI che eventualmente è utile per svolgere l'azione richiesta.

L'esempio tipico di Intent implico è quello che viene richiamato quando vogliamo visualizzare una pagina web:

Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.marcolecce.com/blog/"));

codice 1.3

Il codice 1.3 comunica alla piattaforma Android che vogliamo visualizzare (Intent.ACTION _VIEW) le informazioni rappresentate da uno specifico URI (http://www.marcolecce.com/blog/): nella pratica il sistema Android esegue una ricerca di tutte le componenti che sono registrate per l'esecuzione di una specifica azione ed eventualmente con uno specifico tipo di dato.

Se tale ricerca restituisce un solo componente, allora è il sistema stesso che automaticamente lo esegue, altrimenti se la ricerca identifica più componenti registrati per l'azione richiesta allora il sistema visualizzerà all'utente un dialog in cui verranno mostrate tutte le componenti trovate: sarà quindi compito dell'utente decidere quale componente preferisce per eseguire l'azione da lui richiesta.

Utilizzare gli intent per spedire SMS

Ora dovremmo avere un'idea un pò più chiara di cosa sono e come funzionano gli Intent della piattaforma Android: possiamo allora proseguire il discorso principale e vedere come utilizzare gli Intent per inviare i nostri SMS.

Il codice che analizziamo è il seguente:

Intent smsIntent = new Intent(Intent.ACTION_SENDTO, Uri.parse("sms:123456789"));
smsIntent.putExtra("sms_body", "Un saluto SMS da Marco Lecce");
startActivity(smsIntent);

codice 1.4

Come dimostra chiaramente il codice 1.4, inviare SMS è in realtà molto semplice: la prima cosa da fare è creare un oggetto Intent (smsIntent nel nostro esempio) con l'azione Intent.ACTION_SENDTO specificando il numero di destinazione usando la notazione "sms:" come dati in input per l'Intent.

Il passo successivo è quello di utilizzare un parametro "sms_body" per passare il testo dell'SMS all'Intent: a questo punto non ci rimane che richiamare il metodo startActivity passandogli come parametro l'Intent che abbiamo appena creato e configurato.

Ovviamente il codice 1.4 presenta uno dei casi più semplici di invio di SMS: per non annoiarci troppo possiamo aggiungere un pochetto di complessità e compiere un piccolo passo in avanti, analizzando come sia possibile allegare dei file al nostro SMS creando quello che a tutti gli effetti diventa un MMS (Multimedia Messaging Service).

Vediamo il codice:

// URI del contenuto da allegare
Uri attached_Uri = Uri.parse("content://media/external/images/media/myphoto");
// Creiamo un nuovo Intent per l'MMS
Intent mmsIntent = new Intent(Intent.ACTION_SEND, attached_Uri);
mmsIntent.putExtra("sms_body", "Un saluto MMS da Marco Lecce");
mmsIntent.putExtra("address", "123456789");
mmsIntent.putExtra(Intent.EXTRA_STREAM, attached_Uri);
mmsIntent.setType("image/png");
startActivity(mmsIntent);

codice 1.5

La prima cosa da fare è definire una URI per la risorsa che vogliamo allegare al nostro MMS (attached_Uri nell'esempio - codice 1.5). A questo punto passiamo alla definizione dell'Intent, al quale questa volta passiamo la URI con la risorsa da allegare.

Utilizziamo il metodo putExtra() per configurare il nostro Intent: impostiamo il corpo del messaggio (direttiva "sms_body"), il numero di telefono al quale inviare l'MMS (direttiva "address") e l'extra stream per la risorsa da allegare (direttiva "Intent.EXTRA_STREAM"). La configurazione dell'Intent finisce con l'impostazione del tipo di risorsa che vogliamo allegare (nell'esempio 1.5 è un'immagine PNG) utilizzando il metodo setType().

A questo punto possiamo richiamare il metodo startActivity() passandogli come parametro l'Intent che abbiamo appena creato e configurato.

Invio degli SMS utilizzando l'SMSManager

La seconda metodologia messa a disposizione dalla piattaforma Android per l'invio di SMS è quella che si basa sull'utilizzo dell'SMSManager.

L'SMSManager mette a disposizione dello sviluppatore un insieme di funzionalità che permettono di sostituire in toto quelle messe a disposizione dall'applicazione nativa della piattaforma Android: in altre parole utilizzando l'SMSManager possiamo rimpiazzare l'applicazione nativa degli SMS per l'invio dei messaggi, per gestirne la ricezione o ancora per impletare un layer di trasporto dati.

Come ormai siamo abituati con altri componenti della piattaforma Android, l'utilizzo dell'SMSManger è molto intuitivo, e con poche righe di codice possiamo raggiungere pienamente il nostro obiettivo; anche in questo caso insomma il codice è sostanzialmente autoesplicativo:

SmsManager smsManager = SmsManager.getDefault();
String sendTo = "0123456789";
String message = "Un saluto da Marco Lecce con l'SMS Manager! :)";
smsManager.sendTextMessage(sendTo, null, message, null, null);

codice 1.6

La prima coda da fare è ottenere una reference all'SMSManager usando il metodo statico SmsManger.getDefault(); definiamo poi due variabili stringa, una per il numero di telefono destinatario (variabile sendTo) e una per il messaggio che costituirà il corpo dell'SMS (variabile message).

A questo punto possiamo procedere all'invio dell'SMS utilizzando il metodo sendTextMessage(): il metodo accetta 4 parametri, ovvero l'indirizzo di destinazione, l'indirizzo del service center (impostando a null si indica l'utilizzo del service center di default), il testo del messaggio, il sendIntent e il deliveryIntent.

Gli ultimi due parametri ci permettono di specificare gli Intent per tracciare la trasmissione e il successo della consegna dell'SMS: vedremo tra poco come sfruttare questi strumenti per tracciare e controllare l'invio dell'SMS.

Per chi volesse approfondire o chiarirsi ulteriormente le idee sul metodo sendTextMessage() maggiori dettagli sono disponibili nella pagina della documentazione ufficiale.

Per poter inviare i messaggi SMS la nostra applicazione deve richiederne esplicitamente i permessi. Questo significa che dobbiamo aggiungere al file manifest dell'applicativo la richiesta per utilizzare i permessi SEND_SMS:

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

A questo punto possiamo testare l'invio degli SMS dal nostro applicativo: come abbiamo visto nella parte introduttiva di questo articolo possiamo utilizzare più istanze di emulatori per simulare l'invio e la ricezione degli SMS e osservare così il comportamente della nostra app.

Tracciare l'invio e l'avvenuta ricezione degli SMS

Come abbiamo introdotto nella sezione precedente, l'SMSManager utilizza il metodo sentTextMessage() per gestire l'invio di SMS, ma in realtà questo metodo è decisamente più potente di quello che potrebbe sembrare ad una prima occhiata.

Infatti possiamo creare dei Pending Intent per gestire azioni specifiche e utilizzare i Broadcast Receiver per "ascoltare" le richieste generate da questi Intent: ora, se passiamo gli Intent in questione al metodo sentTextMessage() l'applicativo, opportunamente "registrato" ai Broadcast Receiver specifici, sarà in grado di tenere traccia di quanto sta accadendo al nostro SMS. Per comodità riportiamo nuovamente la signature del metodo sentTextMessage():

sendTextMessage(sendTo, null, message, null, null);

Gli ultimi due parametri ci permettono di specificare i Pending Intent di cui sopra: il primo Pending Intent (penultimo parametro) entra in azione sia quando il messaggio SMS è stato correttamente spedito sia quando l'inoltro fallisce. Il Broadcast Receiver predisposto per ascoltare, ricevere e gestire questo Intent accetta come result code uno dei seguenti valori:

  • Activity.RESULT_OK
  • SmsManager.RESULT_ERROR_GENERIC_FAILURE
  • SmsManager.RESULT_ERROR_RADIO_OFF
  • SmsManager.RESULT_ERROR_NULL_PDU

Activity.RESULT_OK indica il successo della trasmissione: tutti gli altri result code specificano invece una condizione di errore: SmsManager.RESULT_ERROR_GENERIC_FAILURE indica un errore generico, SmsManager.RESULT_ERROR_RADIO_OFF viene inviato quando il telefono risulta non raggiungibile (nello specifico non c'è segnale) mentre SmsManager.RESULT_ERROR_NULL_PDU specifica un problema di PDU (Protocol Description Unit).

Il secondo Pending Intent (ultimo parametro) entra in azione solo dopo che il destinatario ha ricevuto il messaggio SMS: il codice seguente mostra un esempio di come possiamo configurare i Pending Intent e quindi su come sfruttare il metodo sendTextMessage() per una gestione più attenta degli SMS.

PendingIntent sentPI = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent("SENT_SMS_ACTION"), 0);
PendingIntent deliverPI = PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent("DELIVERED_SMS_ACTION"), 0);
registerReceiver(new BroadcastReceiver() {
	@Override
	public void onReceive(Context _context, Intent _intent) {
		switch (getResultCode()) {
			case Activity.RESULT_OK:
				// invio avvenuto con successo
				break;
			case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
				//gestione dell'errore generico
				break;
			case SmsManager.RESULT_ERROR_RADIO_OFF:
				//gestione dell'errore di segnale assente
				break;
			case SmsManager.RESULT_ERROR_NULL_PDU:
				//gestione dell'errore sulla PDU
				break;
		}
		}
	},
	new IntentFilter("SENT_SMS_ACTION"));
registerReceiver(new BroadcastReceiver() {
			@Override
			public void onReceive(Context _context, Intent _intent) {
				//ricezione avvenuta correttamente: posso fornire un feedback 						//appriopriato all'utente
			}
		},
	new IntentFilter("DELIVERED_SMS_ACTION"));
smsManager.sendTextMessage(sendTo, null, myMessage, sentPI, deliverPI);

codice 1.8

Il codice 1.8 mostra un esempio di quanto abbiamo introdotto poc'anzi: la prima cosa che facciamo è definire i due Pending Intent, sentPI e deliverPI.

Successivamente registriamo due BroadcastReceiver: ogni receiver sarà attivato da tutti quegli Intent che hanno la possibilità di gestire il filtro passato come parametro, nel nostro esempio i due IntentFilter da gestire sono "SENT_SMS_ACTION" e "DELIVERED_SMS_ACTION".

Il passo successivo è definire il comportamento della nostra applicazione in fase di invio e ricezione dell'SMS. Vediamo il primo caso.

Nel receiver che abbiamo configurato per monitorare l'invio dell'SMS passiamo un oggetto di tipo BroadcastReceiver, effettuando l' override del metodo onReceive(). Questo metodo viene richiamato quando il BroadcastReceiver che abbiamo appena impostato riceve un Intent broadcast per l'azione "SENT_SMS_ACTION" .

A questo punto abbiamo "insegnato" alla nostra app come comportarsi quando l'invio dell'SMS è avvenuto correttamente e quando arriva la conferma della ricezione da parte del destinatario.

Normalmente questo metodo è chiamato all'interno del thread principale del suo processo, a meno che non venga richiesto esplicitamente la schedulazione in un thread differente usando il metodo registerReceiver() come nel nostro codice di esempio (codice 1.8).

Quando il metodo onReceive() viene richiamato all'interno del thread principale è buona norma evitare di fargli svolgere operazioni particolarmente lunghe visto che da sistema è impostato un timeout di 10 secondi scaduto il quale la piattaforma considera l'attività una candidata ad essere "uccisa".

Nella pratica la sostanza di quanto abbiamo visto non cambia per la configurazione del receiver impostato per il monitoraggio della ricezione dell'SMS da parte del destinatario: dobbiamo tenere presente che all'interno del metodo onReceive() non è possibile creare oggetti di tipo Dialog, pertanto dobbiamo trovare un metodo alternativo per fornire un feedback appropriato all'utente, come ad esempio visualizzare un messaggio in una label o mettere in risalto un'immagine che possa spiegare all'utente cosa sia successo.

Come sappiamo i messaggi SMS normalmente hanno un limite massimo di caratteri, pertanto i messaggi con un numero di caratteri maggiore del limite consetito (in genere 160 caratteri) devono essere opportunamente sezionati in più messaggi.

Ovviamente Android mette a disposizione uno strumento davvero facile ed immediato per gestire questa problematica: il metodo divideMessage() della classe SMSManger.

divideMessage () richiede un parametro stringa, ovvero il testo completo dell'SMS, e restituisce un ArrayList di stringhe in cui ogni elemento è una sezione/sotto-stringa del testo originale che non supera il limite di caratteri consentito per un messaggio SMS. In pratica l'ArrayList contiene una serie di SMS opportunamente ordinati il cui numero di elementi dipende dalla lunghezza del messaggio originale: ad esempio se il messaggio originale è composto da 310 caratteri, divideMessage() restituisce un ArrayList composto da due elementi, il primo di 160 caratteri (lunghezza massima per il testo di un SMS) e il secondo di 150 caratteri.

Una volta sezionato opportunamente il nostro SMS possiamo utilizzare il metodo sendMultipartTextMessage() messo a disposizione dall' SMSManager per trasmettere l'array di messaggi:

String message = […];
ArrayList<String> messageArray = smsManager.divideMessage(message);
ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>();
for (int i = 0; i < messageArray.size(); i++) {
	sentIntents.add(sentPI);
}
smsManager.sendMultipartTextMessage(sendTo, null, messageArray, sentIntents, null);

codice 1.9

Il codice 1.9 mostra un esempio di utilizzo del metodo sentMultipartTextMessage(): nella prima riga definiamo la variabile di tipo stringa che contiene l'intero messaggio dell'SMS. Subito dopo applichiamo il metodo divideMessage(): a questo punto la stringa iniziale message è stata opportunamente sezionata in un ArrayList di stringhe tutte con un numero di caratteri minore o uguale a 160.

Nell'esempio ho inserito un semplice ciclo for che permette di definire un Pending Intent per ogni item dell' ArrayList, ovvero per ogni SMS ricavato dal testo originale: questo per mostrare che i parametri sentIntent e deliveryIntent del metodo sendMultipartTextMessage() sono anch'essi ArrayList che possono essere utilizzati per specificare un differente Pending Intent per ogni parte del messaggio (e quindi per ogni SMS) che chiaramente permettono di monitorare e controllare eventualmente in modo specifico ogni parte del messaggio da inviare.

Bene, ora siamo in grado di inviare messaggi SMS e gestirne il ciclo di vita. Lo scopo dell'articolo era quello di analizzare i meccanismi e le tecniche messe a disposizione dalla piattaforma Android per fornire alla nostra applicazione la possibilità di gestire e controllare l'invio degli SMS.

Come abbiamo visto negli esempi il codice richiesto per implementare queste funzionalità è chiaro, di facile lettura e sicuramente composto da poche righe: questo dimosta ancora una volta la potenza e l'accurata progettazione che caratterizzano la piattaforma Android, la quale mette a disposizione degli sviluppatori strumenti tali da permettere di realizzare in poco tempo applicazioni praticamente di ogni genere.

Ti consigliamo anche