Depurar BroadcastReceiver con Visual Studio Mac

¡Hola!
Hoy vamos con una entrada rápida. En ocasiones, cuando desarrollamos aplicaciones Xamarin Forms (o Xamarin Android), creamos BroadcastReceiver que nos ayudan a recibir ciertos eventos que ocurren. Por ejemplo, nos puede interesar recibir información de cuándo el usuario ha recibido un sms, o que se está agotando la batería… Un ejemplo sencillo de BroadcastReceiver sería el siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
    namespace SampleXFBroadcast.Droid
    {
        [BroadcastReceiver]
        [IntentFilter(new[] { "android.provider.Telephony.SMS_RECEIVED" })]
        public class SMSBroadcastReceiver : BroadcastReceiver
        {
            private static string SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
 
            public override void OnReceive(Context context, Intent intent)
            {
                (string address, string message) smsInfo = (string.Empty, string.Empty);
 
                if (intent.Action == SMS_RECEIVED)
                {
                    smsInfo = GetAddressAndMessageBody(intent);
                }
 
                Intent resultIntent = new Intent(context, typeof(MainActivity));
                var pendingIntent = PendingIntent.GetActivity(context, 0, resultIntent, PendingIntentFlags.UpdateCurrent);
 
                using (var notificationBuilder = new NotificationCompat.Builder(context, "PushNotificationChannel")
                        .SetSmallIcon(context.ApplicationInfo.Icon)
                        .SetContentTitle(smsInfo.address)
                        .SetContentText(smsInfo.message)
                        .SetAutoCancel(true).SetContentIntent(pendingIntent))
                {
                    NotificationManager notificationManager = (NotificationManager)context.GetSystemService(Context.NotificationService);
                    notificationManager.Notify(string.Empty, 0, notificationBuilder.Build());
                }
            }
 
            private (string address, string message) GetAddressAndMessageBody(Intent intent)
            {
                var bundle = intent.Extras;
                var smsMessage = Telephony.Sms.Intents.GetMessagesFromIntent(intent)[0];
                return (smsMessage.OriginatingAddress, smsMessage.DisplayMessageBody);
            }
        }
    }

Lo que hacemos en este BroadcastReceiver es, escuchar cuando se recibe un sms y lanzar una notificación.
En el caso de este BroadcastReceiver es bastante sencillo de probar, por lo menos en un emulador, ya que casi todos tiene la opción de enviar un sms de prueba. Pero, qué pasaría si queremos lanzar el BroadcastReceiver que indica que se ha encendido el teléfono (BOOT_COMPLETED), ¿reiniciamos el teléfono cada vez que lo queremos probar? O si queremos depurar un BroadcastReceiver si haber lanzado la aplicación con el depurador (es decir, con la aplicación muerta). Pues VSMac nos lo pone bastante sencillo. Dentro de Run/Run with/Custom Configuration nos da muchas opciones para poder lanzar y depurar un BroadcastReceiver, entre otras cosas:
Para el caso anterior, nos interesa lanzar nuestro SMSBroadcastReceiver:
Bueno, este no ha sido un buen ejemplo 🙂 Si vemos la salida de la aplicación nos dice esto:

> am broadcast -a "android.provider.Telephony.SMS_RECEIVED" -n "com.companyname.samplexfbroadcast/md540e7f64f4a8780ff592474c666e5380a.SMSBroadcastReceiver" --include-stopped-packages
> Broadcasting: Intent { act=android.provider.Telephony.SMS_RECEIVED flg=0x400020 cmp=com.companyname.samplexfbroadcast/md540e7f64f4a8780ff592474c666e5380a.SMSBroadcastReceiver }
Security exception: Permission Denial: not allowed to send broadcast android.provider.Telephony.SMS_RECEIVED from pid=8991, uid=2000

java.lang.SecurityException: Permission Denial: not allowed to send broadcast android.provider.Telephony.SMS_RECEIVED from pid=8991, uid=2000
	at com.android.server.am.ActivityManagerService.broadcastIntentLocked(ActivityManagerService.java:19197)
	at com.android.server.am.ActivityManagerService.broadcastIntent(ActivityManagerService.java:19835)
	at com.android.server.am.ActivityManagerShellCommand.runSendBroadcast(ActivityManagerShellCommand.java:621)
	at com.android.server.am.ActivityManagerShellCommand.onCommand(ActivityManagerShellCommand.java:154)
	at android.os.ShellCommand.exec(ShellCommand.java:96)
	at com.android.server.am.ActivityManagerService.onShellCommand(ActivityManagerService.java:15014)
	at android.os.Binder.shellCommand(Binder.java:594)
	at android.os.Binder.onTransact(Binder.java:492)
	at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:4243)
	at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2919)
	at android.os.Binder.execTransact(Binder.java:697)

Vamos, que no tenemos permiso para lanzar android.provider.Telephony.SMS_RECEIVED. Vamos a cambiar un poco el código que teníamos para poder lanzar y depurar nuestro SMSBroadcastReceiver:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
    namespace SampleXFBroadcast.Droid
    {
        [BroadcastReceiver]
        [IntentFilter(new[] { "android.provider.Telephony.SMS_RECEIVED" })]
        public class SMSBroadcastReceiver : BroadcastReceiver
        {
            private static string SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
            private static string FAKE_SMS_RECEIVED = "FAKE_SMS_RECEIVED";
 
            public override void OnReceive(Context context, Intent intent)
            {
                (string address, string message) smsInfo = (string.Empty, string.Empty);
 
                if (intent.Action == SMS_RECEIVED)
                {
                    smsInfo = GetAddressAndMessageBody(intent);
                }
                if(intent.Action == FAKE_SMS_RECEIVED)
                {
                    smsInfo = ("Fake Number", "Fake message");
                }
 
                Intent resultIntent = new Intent(context, typeof(MainActivity));
                var pendingIntent = PendingIntent.GetActivity(context, 0, resultIntent, PendingIntentFlags.UpdateCurrent);
 
                using (var notificationBuilder = new NotificationCompat.Builder(context, "PushNotificationChannel")
                        .SetSmallIcon(context.ApplicationInfo.Icon)
                        .SetContentTitle(smsInfo.address)
                        .SetContentText(smsInfo.message)
                        .SetAutoCancel(true).SetContentIntent(pendingIntent))
                {
                    NotificationManager notificationManager = (NotificationManager)context.GetSystemService(Context.NotificationService);
                    notificationManager.Notify(string.Empty, 0, notificationBuilder.Build());
                }
            }
 
            private (string address, string message) GetAddressAndMessageBody(Intent intent)
            {
                var bundle = intent.Extras;
                var smsMessage = Telephony.Sms.Intents.GetMessagesFromIntent(intent)[0];
                return (smsMessage.OriginatingAddress, smsMessage.DisplayMessageBody);
            }
        }
    }

Ahora ya vamos a poder depurarlo con la siguiente configuración:
Si ahora pulsamos en Debug, vamos a ver cómo se ha lanzado nuestra notificación fake:
Si ahora ponemos un punto de interrupción, veremos que, efectivamente, estamos pudiendo depurar nuestro BroadcastReceiver con la app muerta:
De esta manera, hemos visto que lo vamos a poder depurar sin problemas y no vamos a necesitar poner trazas para ver qué ocurre cuando se lanza mientras la aplicación está muerta (sí, lo he hecho antes de conocer esta opción…). Además de poder depurar BroadcastReceivers, tenemos la opción de depurar servicios, actividades y configurar muchas opciones para personalizar lo que queramos al lanzarlos. Estos mismos parámetros los vamos a poder configurar desde las opciones del proyecto, para así dejarlos configurados entre depuración y depuración:

¡Un saludo!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *