miércoles, 22 de abril de 2015

Gif animado en aplicación android

Para agregar un gif animado a nuestra aplicación debemos empezar por tener el archivo .gif en la carpeta res/drawable, para este ejemplo el archivo se llama loading.gif.

Después en la carpeta res/values creamos el archivo attrs.xml con el siguiente contenido:


1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="GifView">
        <attr name="gif_source" format="reference"/>
    </declare-styleable>
</resources>

Si el archivo ya existe sólo agregue la sección declare-styleable, esto sirve para que nuestro control tenga una propiedad 'gif_source' que hará referencia al archivo .gif.

Creamos también en la carpeta src un paquete llamado com.androideazteca.controls y dentro de el una clase a la que llamaremos GifView.java con el siguiente contenido:

 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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package com.androideazteca.controls;


import java.io.InputStream;

import com.androideazteca.animatedgif.R;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Movie;
import android.util.AttributeSet;
import android.view.View;

public class GifView extends View {

 private InputStream gifInputStream;
 private Movie gifMovie;
 private int movieWidth, movieHeight;
 private long movieDuration;
 private long mMovieStart;

 @SuppressLint("Recycle")
 public GifView(Context context, AttributeSet attrs) {
  super(context, attrs);

  TypedArray attributes = context.obtainStyledAttributes(attrs,
    R.styleable.GifView, 0, 0);
  int src = attributes.getResourceId(R.styleable.GifView_gif_source, -1);

  gifInputStream = context.getResources().openRawResource(src);

  init(context);
 }

 private void init(Context context) {
  
  setFocusable(true);

  gifMovie = Movie.decodeStream(gifInputStream);
  movieWidth = gifMovie.width();
  movieHeight = gifMovie.height();
  movieDuration = gifMovie.duration();
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  
  setMeasuredDimension(movieWidth, movieHeight);
 }

 public int getMovieWidth() {
  
  return movieWidth;
 }

 public int getMovieHeight() {
  
  return movieHeight;
 }

 public long getMovieDuration() {
  
  return movieDuration;
 }

 @Override
 protected void onDraw(Canvas canvas) {

  long now = android.os.SystemClock.uptimeMillis();
  
  if (mMovieStart == 0) { // first time
   
   mMovieStart = now;
  }

  if (gifMovie != null) {

   int dur = gifMovie.duration();
   
   if (dur == 0) {
    
    dur = 1000;
   }

   int relTime = (int) ((now - mMovieStart) % dur);

   gifMovie.setTime(relTime);
   gifMovie.draw(canvas, 0, 0);
  
   invalidate();
  }

 }
}

Recuerden cambiar el nombre del paquete en la línea número 4:

import com.androideazteca.animatedgif.R;

Por último agregamos el siguiente contenido a la vista donde deseamos que aparezca el gif:

1
2
3
4
5
6
7
8
<com.androideazteca.controls.GifView
        xmlns:androideazteca="http://schemas.android.com/apk/res/com.androideazteca.animatedgif"
        android:id="@+id/gifviewLoading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        androideazteca:gif_source="@drawable/loading" />

También en este fragmento es necesario cambiar el nombre del paquete en las líneas 1 y 2.

En la línea 8 se hace referencia al atributo 'gif_source' que creamos en el archivo attrs.xml, el valor es "@drawable/loading", la palabra 'loading' es el nombre del gif que está almacenado en la carpeta drawable. Para mostrar otro gif solo deben copiarlo a la carpeta drawable y cambiar el valor del atributo gif_source, poniendo el nombre del gif (sin extensión). Ejemplo:

androideazteca:gif_source="@drawable/otrogif"


Por último debemos modificar el archivo AndroidManifest.xml agregando la siguiente línea:

android:hardwareAccelerated="false"

en el activity donde usamos el control para mostrar el gif

en mi caso ha quedado así:


 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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.androideazteca.animatedgif"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="21" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:hardwareAccelerated="false"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Pueden encontrar un código de ejemplo en esta liga:

AnimatedGif.zip


Espero que les sea de utilidad.

Saludos!

jueves, 19 de diciembre de 2013

Agregar TableRow's dinámicamente a un TableLayout

Tratando de dar continuidad a mi blog voy a escribir sobre un tema mucho mas común que el anterior: Agregar filas dinámicamente a un TableLayout.

Primero creamos un .xml en la carpeta res/layout con la estructura que ha de tener cada una de las filas a agregar, en mi caso la fila debe tener tres columnas, la primera con una etiqueta para un número secuencial, la segunda con un signo de pesos y una etiqueta para un monto y la tercera con una etiqueta para una fecha.

he nombrado a mi archivo row_repayment_plan.xml y el contenido es 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?xml version="1.0" encoding="utf-8"?>
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <TextView
        android:id="@+id/index"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@drawable/rectangle_border"
        android:gravity="center"
        android:paddingBottom="5dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="5dp"
        android:textAppearance="?android:attr/textAppearanceSmall" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@drawable/rectangle_border" >
        
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="left"
            android:paddingBottom="5dp"
            android:paddingLeft="10dp"
            android:paddingRight="10dp"
            android:paddingTop="5dp"
            android:text="$"
            android:textAppearance="?android:attr/textAppearanceSmall" />
        
        <TextView
            android:id="@+id/amount"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="right"
            android:paddingBottom="5dp"
            android:paddingLeft="10dp"
            android:paddingRight="10dp"
            android:paddingTop="5dp"
            android:textAppearance="?android:attr/textAppearanceSmall" />

    </LinearLayout>

    <TextView
        android:id="@+id/date"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:background="@drawable/rectangle_border"
        android:gravity="center"
        android:paddingBottom="5dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="5dp"
        android:textAppearance="?android:attr/textAppearanceSmall" />

</TableRow>

Notese el uso de android:background="@drawable/rectangle_border", es un archivo .xml para ponerle un borde rectangular a cada "celda".

Para esto se crea el archivo rectangle_border.xml en la carpeta res/drawable con el siguiente contenido:

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="rectangle"
  xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@android:color/transparent" />
    <stroke android:width="1.0dip" android:color="#ff000000" />
</shape>

También usamos la propiedad android:layout_weight, esta sirve para distribuir el espacio de la fila, en este caso las tres columnas tendran el mismo ancho, por eso tenemos android:layout_weight="1" en los tres casos, para que esta propiedad funcione correctamente debemos tener android:layout_width="0dp"

 Después incluimos en el xml de nustro activity el TableLayout al que vamos a agregarle filas con un código como 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
         <TableLayout
            android:id="@+id/repaymentTable"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >

            <TableRow
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/DarkGray" >

                <TextView
                    android:id="@+id/index"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="@drawable/rectangle_border"
                    android:gravity="center"
                    android:paddingBottom="5dp"
                    android:paddingLeft="10dp"
                    android:paddingRight="10dp"
                    android:paddingTop="5dp"
                    android:text="@string/number_of_payments"
                    android:textAppearance="?android:attr/textAppearanceSmall"
                    android:textColor="@color/White"
                    android:textColorLink="@color/White" />

                <TextView
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="@drawable/rectangle_border"
                    android:gravity="center"
                    android:paddingBottom="5dp"
                    android:paddingLeft="10dp"
                    android:paddingRight="10dp"
                    android:paddingTop="5dp"
                    android:text="@string/amount_of_payment"
                    android:textAppearance="?android:attr/textAppearanceSmall"
                    android:textColor="@color/White" />

                <TextView
                    android:id="@+id/date"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="@drawable/rectangle_border"
                    android:gravity="center"
                    android:paddingBottom="5dp"
                    android:paddingLeft="10dp"
                    android:paddingRight="10dp"
                    android:paddingTop="5dp"
                    android:text="@string/scheduled_payment_date"
                    android:textAppearance="?android:attr/textAppearanceSmall"
                    android:textColor="@color/White" />

            </TableRow>
        </TableLayout>

Este TableLayout ya contiene un TableRow que hará las veces de cabecera de la tabla, con la misma estructura que el TableRow que agregaremos dinámicamente.

Por último en el .java de nustro activity ponemos el código que agregará las filas:

 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
                try {
			
			Intent actualIntent = getIntent();
			
			JSONArray payments = new JSONArray(actualIntent.getStringExtra("payments"));

			TableLayout table = (TableLayout) this.findViewById(R.id.repaymentTable);

			for (int i = 0; i < payments.length(); i++) {

				TableRow row = (TableRow) LayoutInflater.from(this).inflate(R.layout.row_repayment_plan, null);
				
				if(i % 2 != 0) {
					
					row.setBackgroundColor(Color.LTGRAY);
				}

				((TextView) row.findViewById(R.id.index)).setText(Integer.toString(i + 1));
				((TextView) row.findViewById(R.id.amount)).setText(payments.getJSONObject(i).getString("amount"));
				((TextView) row.findViewById(R.id.date)).setText(payments.getJSONObject(i).getString("date"));
					
				table.addView(row);
			}
		} catch (JSONException exception) {
			
			Logger4J.error(getClass(), exception.getMessage());
		}

En mi caso la información que se va a poner en la tabla llega como un extra del Intent, con la llave payments.

El valor de este extra es un arreglo de objetos JSON como el siguiente:

[{"amount": "115.10", "date": "02/10/2013"}, {"amount": "115.10", "date": "02/12/2013"}, {"amount": "115.10", "date": "02/14/2013"}, {"amount": "115.10", "date": "02/16/2013"}]

Esta forma de poner los datos es opcional, se puede usar cualquier colección que convenga a sus necesidades.

Luego se recorre la colección de datos y por cada elemento se crea un TableRow a partir de la plantilla que creamos previamente, en este caso identificada por R.layout.row_repayment_plan


De esa plantilla extraemos los TextView's y les asignamos el valor que les corresponda.

He usado el índice i para poner fondo gris claro a las filas impares.

Al final agregamos la fila a la tabla con table.addView(row);


Espero que esto les sea de utilidad.

Gracias y saludos!!



jueves, 13 de junio de 2013

Websocket Android-PC

Pues bien, parece que como bloggero me muero de hambre, pero que se le va a hacer, el trabajo se ha puesto muy pesado. Lo bueno es que es durante estos periodos de trabajo extenuante cuando mas se aprende.

La aplicación que estamos desarrollando en el trabajo implica comunicación de una página web en la computadora con una aplicación en el dispositivo Android, conectado con el cable USB y utilizando el modo Debug, en un principio intenté utilizando un applet cargado en la página web, que se conectara como cliente por medio de Sockets a un servidor en la aplicación Android, funcionó bien en mi página de pruebas, pero cuando lo pasé al sitio web real tenía problemas al pasar de una página a otra, es decir, al desconectar un cliente y conectar otro, ademas el uso de applets implica que se tenga instalada una máquina virtual de java en la computadora, que se tenga que firmar el applet y que el usuario deba dar permisos, cosa que no le gustó al super-jefe.

Por esta razón seguí investigando y encontré que se podía utilizar Web Sockets con lenguaje javaScript y aquí está el resultado.

Para empezar, en el proyecto android se importa la librería  java_websocket.jar

Después, adapté el código de AutobahnServerTest.java para utilizarlo como una clase en el proyecto android, quedando así el código:


 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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.jpiedra.utilities;

import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Collection;

import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;

public class ServerWebSocket extends WebSocketServer {
 private static int counter = 0;

 public ServerWebSocket(int port) throws UnknownHostException {
  super(new InetSocketAddress(port));
 }

 @Override
 public void onOpen(WebSocket conn, ClientHandshake handshake) {
  counter++;
  System.out.println("///////////Opened connection number" + counter);
 }

 @Override
 public void onClose(WebSocket conn, int code, String reason, boolean remote) {
  System.out.println("closed");
 }

 @Override
 public void onError(WebSocket conn, Exception ex) {
  System.out.println("Error:");
  ex.printStackTrace();
 }

 @Override
 public void onMessage(WebSocket conn, String message) {
  conn.send(message);
 }

 @Override
 public void onMessage(WebSocket conn, ByteBuffer blob) {
  conn.send(blob);
 }

 /**
  * Sends <var>text</var> to all currently connected WebSocket clients.
  * 
  * @param text
  *            The String to send across the network.
  * @throws InterruptedException
  *             When socket related I/O errors occur.
  */
 public void sendToAll(String text) {
  Collection<WebSocket> con = connections();
  synchronized (con) {
   for (WebSocket c : con) {
    c.send(text);
   }
  }
 }
}

Si revisan el código original verán que los cambios son mínimos:

    Queda un único constructor que recibe un puerto.
    Se elimina la función onWebsocketMessageFragment
    Se elimina la función main
    Se cambia el nombre para entender mejor mi código :)
   
A continuación se crea el archivo SocketConnection.java con el siguiente código


 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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.jpiedra.utilities;

import java.net.UnknownHostException;
import java.util.Observable;

import org.java_websocket.WebSocket;

import com.jpiedra.utilities.ServerWebSocket;

/**
 * The Class SocketConnection.
 * @Autor J Jesús Piedra Chávez
 * @Version 1.0 (05-22-2013)
 */

public class SocketConnection extends Observable {

 private final int PORT = 38300;
 protected ServerWebSocket server;
 public String contentInfo;
 private static SocketConnection socketConnection = null;

 /**
  * Instantiates a new server
  */
 protected SocketConnection() {

  try {
   server = new ServerWebSocket(PORT) {
    public void onMessage(WebSocket conn, String message) {
     contentInfo = message;
     setChanged();
     notifyObservers();
    }
   };
   server.start();
  } catch (UnknownHostException e) {
   
   e.printStackTrace();
  }
 }

 static public SocketConnection getInstance() {
  
  if (socketConnection == null) {
   socketConnection = new SocketConnection();
  }
  return socketConnection;
 }

 /**
  * Send data. Send the message
  * 
  * @param message
  *            the information
  */
 public void sendData(String message) throws Exception {

  server.sendToAll(message);
 }
}

Para este también me base en un ejemplo de chat java usando Sockets, pero ya no me acuerdo de donde lo saqué y la verdad es que ya está muy diferente :)

Los puntos importantes en este código son:

    La clase extiende Observable para poder comunicarse con cualquier vista, agregada previamente a sus observers.
    Se sobreescribe el metodo onMessage del objeto server, para poder hacer algo cuando se recibe un mensaje.
    Se usa una función getInstance() para tener una sola instancia de la clase, es decir un singleton.
   
En el .java de la vista que hará algo con los mensajes recibidos y que enviará mensajes tenemos este código:


 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
import com.jpiedra.utilities.SocketConnection;


/**
 * The Class ScreenSaverActivity.
 * 
 * @Autor J Jesús Piedra Chávez
 * @Version 1.0 (05-22-2013)
 */

public class ScreenSaverActivity extends Activity implements Observer {

 public SocketConnection socketConnection;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_screensaver);

  socketConnection = SocketConnection.getInstance();
  socketConnection.deleteObservers();
  socketConnection.addObserver(this);
 }

@Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }

 @Override
 public void update(Observable observable, Object data) {

        }

    En este se declara a la vista en cuestión como observer del objeto socketConnection.
    Se usa el método update(Observable observable, Object data) para recibir mensajes, es aqui donde entra la funcionalidad del observer. En este caso el mensaje recibido se almacena en la variable socketConnection.contentInfo, es esta información la que debemos manipular.
    Para enviar mensajes se usa el metodo sendData(message) del objeto socketConnection.
   
Por último, en un archivo javaScript, o directamente en un script dentro de la página, se coloca el siguiente código:


 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
45
46
47
48
var ws;

window.onload = function() {
 if (!window.WebSocket) {
  alert("FATAL: WebSocket not natively supported.");
 } else {
  connect();
 }
};

window.onunload = function() {
 disconnect();
};

function connect() {
 ws = new WebSocket("ws://127.0.0.1:38300");
 ws.onopen = function() {
  androidStatus("READY");
 };
 ws.onmessage = function(e) {
  receiveMessage(e.data);
 };
 ws.onclose = function() {
  androidStatus("STOPPED");
  ws = null;
 };
}

function sendMessage(message) {
 if (ws) {
  ws.send(message);
 }
}

function disconnect() {
 if (ws) {
  ws.close();
  ws = null;
 }
}

function receiveMessage(message) {

}

function androidStatus(message) {

}

    En window.onload se crea la conección.
    La función androidStatus se ejecuta al establecer conección, con el mensaje READY, y al desconectarse, con el mensaje STOPPED.
    Se usa la función sendMessage(message) para enviar mensajes.
    Se usa la función receivedMessage(message) para manipular los mensajes recibidos, desde aquí podemos por ejemplo lanzar una alerta con este mensaje :)

Como mencioné al principio, para que todo esto funcione el dispositivo Android debe estar en modo debug, además debe ejecutarse el siguiente comando ADB:

adb forward tcp:38300 tcp:38300 cada vez que se reconecte el dispositivo.

La aplicación en el dispositivo Android debe estar iniciada antes de abrir la página web.


Espero que lessea de ayuda.

P.D. El código lo puse con ayuda de una página llamada Source code beautifier, está chida :)

lunes, 29 de abril de 2013

Conocimiento Libre

Como ya mencioné en la bienvenida soy partidario del software libre, no muy activo pero hago lo que puedo, poner Linux en las máquinas de mi familia, defender android ante las críticas de mi jefe fanboy de Mac, y ya que estamos en este blog debo continuar recomendando libros libres.

Me encontré con una muy buena biblioteca, que por suerte aún no está bloqueada en la red del trabajo :), su nombre es Open Libra, en el vínculo pueden encontrar gran variedad de libros para descargar en zip o ver en línea en formato PDF, entre ellos hay varios sobre Android y en español que pueden encontrarse utilizando el sistema de búsqueda con el que cuenta la página.

El primer libro que llamó mi atención y descargué, por estar en un lenguaje no demasiado técnico, fue Curso de Programación Android.

Con este libro tenemos un buen punto de partida, pues empieza por describir la instalación del IDE de Android, que es un eclipse con plugins extras. En el caso de estarse iniciando es recomendable utilizar ADT-Bundle que viene ya con todo lo necesario para empezar a desarrollar aplicaciones Android. Basta con descomprimir el archivo descargado, ir a la carpeta "Eclipse" y ejecutar la aplicación "eclipse" y tendremos el entorno de desarrollo necesario.

Si eres un desarrollador Java que ya cuenta con un eclipse adaptado a sus necesidades y con plugins a tu gusto, es preferible instalar SDK Tools. En tal caso puedes seguir las instrucciones que vienen en el libro recomendado más arriba :)

Si quieren evitarse la fatiga de buscar libros de programación Android en español les dejo un par de vínculos:

Curso de programación en Android para principiantes
Curso Android - Desarrollo de aplicaciones moviles.

Bienvenidos!

Santas y buenas tardes tengan todos ustedes, yo soy El Conde.

Mi experiencia profesional es mayormente iOS y .Net, como partidario del Software Libre utilizo Linux en mi laptop y Android en mi teléfono celular desde hace varios años, sin embargo llevo relativamente poco tiempo en el desarrollo Android.

Es por eso que he decidido crear este blog, con la esperanza de que las soluciones que he encontrado a los problemas que se me han presentado le sirvan a otras personas que, como yo, tengan poca experiencia.

El nombre del blog lo elegí por dos razones, la primera y mas obvia es que soy mexicano, mi ascendencia es Otomí y la cultura más representativa de mi país es la Azteca.

La segunda es que los desarrollos que llevo a cabo son para una conocida empresa, también mexicana, que en el nombre lleva la palabra "Azteca" y que es directamente responsable de que por fin pueda desarrollar para Android y, por lo tanto, de que tenga algo que publicar aquí.

Espero que lo que publique en este blog ayude al menos a un desarrollador.