Android SMS-PingPong

För några veckor sedan var jag på Crisp Hack Summit (en och en halv dag valfritt hackande) och då jag jobbar med Android-utveckling kände jag för att testa skriva en liten app som man kan kommunicera med via SMS. Det är ju inga större problem att kommunicera med en telefon om man har en fungerande IP-förbindelse, men om man inbart har 2G så blir det lite värre. Dagens prepaid abonemang har oftast en obegränsad mängd med SMS (åtminstone om man inte är tonåring) som man kan skicka. Så varför inte göra en app som när man skickar strängen “ping” svarar med “pong” 🙂

I Android är det inga problem att få en app att ta emot ett SMS samt att ge en app högre prioritet än den inbyggda SMS-appen. Det gör att man kan få telefonen att ta emot ett SMS utan att några notifieringar visas.
För att en app ska få ta emot och skicka SMS (eller göra någonting överhuvudtaget) måste man ge den rätt “permission”s.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="se.birabirro.android.smscom"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="15" />

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

    <application
	android:label="@string/app_name">
        <receiver
            android:name=".SmsReceiver"
            android:exported="true">
            <intent-filter android:priority="100">
		<action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
	</receiver>
     </application>
</manifest>

För att kroka in sin egen kod i operativsystemet så att den ges chansen att ta emot SMS måste vi skapa en BroadcastReceiver samt peka ut den i manifestet ovan via ‘android:name=”.SmsReceiver”‘. Vi måste även sätta ett intent-filter där vi ger just vår SMS-mottagare en hög prioritet så att vår kod ges chansen att läsa SMS:et samt att konsumera det så att det inte går vidare till den vanliga SMS-mottagaren.
När sedan ett SMS tas emot av telefonen kommer detta att ges till vår kod via metoden onReceive. I just detta exempel kontrollerar vi enbart att texten som skickas är strängen “ping” och då skickar vi tillbaka ett svars-SMS med texten “pong”.

public class SmsReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(final Context context, final Intent intent) {
        for (final SmsMessage msg : getMessagesFromIntent(intent)) {
            handleMessage(msg);
        }
    }

    private void handleMessage(final SmsMessage msg) {
        if (isPingCommand(getCommandFromMessage(msg))) {
            sendPongReply(msg.getOriginatingAddress());
            abortBroadcast();
        }
    }

    private static String getCommandFromMessage(final SmsMessage msg) {
        return msg.getMessageBody().trim().toLowerCase();
    }

    private static boolean isPingCommand(final String command) {
        return "ping".equals(command);
    }

    private static void sendPongReply(final String address) {
        SmsManager.getDefault().sendTextMessage(
            address, null, "pong", null, null
        );
    }

    private static List<SmsMessage> getMessagesFromIntent(final Intent intent) {
        final List<SmsMessage> list = new ArrayList<SmsMessage>();
        for (final Object pdu : getPdusFromIntent(intent)) {
            list.add(SmsMessage.createFromPdu((byte[]) pdu));
        }
        return list;
    }

    private static Object[] getPdusFromIntent(final Intent intent) {
        return (Object[]) intent.getExtras().get("pdus");
    }
}

Mycket mer än detta behövs inte för att få till lite SMS ping-pong.

Ett komplett Eclipse-projekt med denna kod finns att ladda ner från github:
https://github.com/thyberg/SmsPingPong
I denna finner du bland annat en väldigt kort SmsActivity-klass.

public class SmsActivity extends Activity {
    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        finish();
    }
}

Vad har man för nytta av denna med i princip enbart ett anrop till “finish()” kan man fråga sig? Jo, sedan Android 3.0 måste en användare starta en given app minst en gång innan den kan ta emot Broadcasts och har man en aktivitet får appen en ikon och kan då startas manuellt. Detta ändrades för att höja säkerheten då en användare så att säga “godkänner” appen genom att starta den själv.
Har man ingen aktivitet i sin app så kan man inte starta den explicit och därmed inte godkänna den, vilket gör att man kan få klia sig mycket i huvudet och undra varför man inte får några broadcasts i sin broadcast receiver… 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.