Skip to content
Archive of posts filed under the Android category.

Tutto il testing possibile per Android al BetterSoftware 2011

Ecco la registrazione del mio intervento “Tutto il testing possibile per Android”, che ho tenuto durante il BetterSoftware a Firenze. Purtroppo 20 minuti per il mio intervento sono stati veramente pochi (ne avevo chiesti il doppio), quindi mi sono dovuto adattare con quello che avevo, tagliando e accorciando come un forsennato. Scusatemi :(

Qui il link diretto al file video, mentre di seguito le slide.

Conflitti nella UX delle Google Android App

Magari ingenuamente, uno si puo’ anche aspettare che le applicazioni fatte da Google per Android seguano una UX comune e, tra loro, abbiano una buona coerenza nell’interfaccia. E invece ecco cosa capita oggigiorno.

Google ShopperGoogle Shopper: spunta una Navigation Bar in basso, in pieno stile iPhone TabBar, mai vista in altre app Google.

Google MusicGoogle Music: Nel secondo screenshot il menu’ contestuale dell’elemento e’ completamente diverso da come appare nei contatti e da come avevano caldamente consigliato al Google IO del 2010. L’Action Bar e’ assente e spunta una Navigation Bar in alto, a scorrimento orizzontale, che ricorda molto l’interfaccia Panorama di WP7, dove per indicare che c’e’ qualcosa a destra e a sinistra si lascia, appunto, troncato il titolo di quello che c’e’ ai lati.

GooglePlusGoogle+ utilizza l’Action Bar, mentre la Navigation Bar appena sotto e’ molto diversa da quanto visto fino ad oggi. Difficile da leggere e da navigare al tocco, conta comunque sullo swipe delle schermate verso destra e verso sinistra, indicando con delle freccine che ci sono ancora contenuti ai quali accedere ai lati della vista su cui si e’ focalizzati.

Continue reading ‘Conflitti nella UX delle Google Android App’ »

Android Advanced Programming al Codemotion

Online video e slide dell’intervento che ho fatto a Marzo durante il Codemotion: “Sopravvivere felicemente ad un anno e mezzo di sviluppo Android”

Continue reading ‘Android Advanced Programming al Codemotion’ »

Appcelerator Titanium su Ubuntu 10.10 64bit

Incuriosito dalla possibilita’ di sviluppare applicazioni multipiattaforma per Android e iPhone, ho provato ad installare Appcelerator Titanium sulla mia Linuxbox, una Ubunto 10.10 a 64 bit. Di seguito i passi seguiti.

Ho scaricato il runtime di Titanium Developer per Linux 64 bit. La versione corrente e’ la 1.2.2. L’ho scompattato e l’ho lanciato. Alla richiesta del percorso di installazione, sotto la mia home oppure in /opt/titanium, ho scelto la seconda opzione, e il programma si e’ chiuso senza altri segni di vita. Per farlo funzionare, sono stato costretto a far partire l’installer con
sudo ./Titanium\ Developer
eseguendo poi a fine installazione un cambio di permessi delle cartelle create con il comando
sudo chown -R rainbowbreeze:rainbowbreeze /opt/titanium

Scaricati i due pacchetti necessari, l’installer si chiude di nuovo con l’errore
symbol lookup error: /usr/lib/libgdk-x11-2.0.so.0: undefined symbol: g_malloc_n
che sembra essere ben documentato e risolto:
cd /opt/titanium (oppure cd ~/.titanium se l’avete installato nella vostra home)
rm runtime/linux/1.0.0/libgobject-2.0.*
rm runtime/linux/1.0.0/libglib-2.0.*
rm runtime/linux/1.0.0/libgio-2.0.*
rm runtime/linux/1.0.0/libgthread-2.0.*

Superato anche questo ostacolo, ho rilanciato l’installazione, accettato le condizioni di contratto e mi trovo davanti un nuovo errore:
icedteanp plugin error: Failed to run /etc/alternatives/../../bin/java.  For more detail rerun “firefox -g” in a terminal window.
anche questo errore e’ documentato e risolto:
sudo aptitude remove openjdk-6-jre icedtea6-plugin
sudo aptitude install sun-java6-jre sun-java6-plugin sun-java6-fonts

Continue reading ‘Appcelerator Titanium su Ubuntu 10.10 64bit’ »

Forzare l’uso della connessione mobile (3G o GPRS) con il WiFi in Android

In Android, l’attivazione della connessione WiFi porta immediatamente alla disattivazione delle altre connessioni, compresa quella mobile, e tutto il traffico dati viene instradato su di essa. Assieme ad una serie di vantaggi, come l’indubbio risparmio per connessioni dati a consumo e il guadagno di velocita’, questo comportampo porta ad alcuni problemi, principalmente:

  • La sospensione senza preavviso di connessioni gia’ in piedi sulla rete dati mobile
  • L’impossibilita’ di accedere a quei servizi legati alla SIM. Ad esempio, un operatori telefonico potrebbe effettuano un accredito direttamente all’utente basandosi sulla SIM che genera il traffico. Con il Wifi, non si puo’ ottenere questa informazione.

Quindi, per quanto l’utilita’ di forzare l’uso della connessione dati mobile per raggiungere un certo indirizzo all’interno della proprio applicazione possa sembrare scontata, purtroppo Android non mette a disposizione nessuna API documentata e sicura per farlo, ma occorre passare per una serie di workaround che funzionano solo dalla versione 2.2.

/**
 * Enable mobile connection for a specific address
 * @param context a Context (application or activity)
 * @param address the address to enable
 * @return true for success, else false
 */
private boolean forceMobileConnectionForAddress(Context context, String address) {
    ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    if (null == connectivityManager) {
        Log.debug(TAG_LOG, "ConnectivityManager is null, cannot try to force a mobile connection");
        return false;
    }

    //check if mobile connection is available and connected
    State state = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
    Log.debug(TAG_LOG, "TYPE_MOBILE_HIPRI network state: " + state);
    if (0 == state.compareTo(State.CONNECTED) || 0 == state.compareTo(State.CONNECTING)) {
        return true;
    }

    //activate mobile connection in addition to other connection already activated
    int resultInt = connectivityManager.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, "enableHIPRI");
    Log.debug(TAG_LOG, "startUsingNetworkFeature for enableHIPRI result: " + resultInt);

    //-1 means errors
    // 0 means already enabled
    // 1 means enabled
    // other values can be returned, because this method is vendor specific
    if (-1 == resultInt) {
        Log.error(TAG_LOG, "Wrong result of startUsingNetworkFeature, maybe problems");
        return false;
    }
    if (0 == resultInt) {
        Log.debug(TAG_LOG, "No need to perform additional network settings");
        return true;
    }

    //find the host name to route
    String hostName = StringUtil.extractAddressFromUrl(address);
    Log.debug(TAG_LOG, "Source address: " + address);
    Log.debug(TAG_LOG, "Destination host address to route: " + hostName);
    if (TextUtils.isEmpty(hostName)) hostName = address;

    //create a route for the specified address
    int hostAddress = lookupHost(hostName);
    if (-1 == hostAddress) {
        Log.error(TAG_LOG, "Wrong host address transformation, result was -1");
        return false;
    }
    //wait some time needed to connection manager for waking up
    try {
        for (int counter=0; counter<10; counter++) {
            State checkState = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
            if (0 == checkState.compareTo(State.CONNECTED))
                break;
            Thread.sleep(1000);
        }
    } catch (InterruptedException e) {
        //nothing to do
    }
    boolean resultBool = connectivityManager.requestRouteToHost(ConnectivityManager.TYPE_MOBILE_HIPRI, hostAddress);
    Log.debug(TAG_LOG, "requestRouteToHost result: " + resultBool);
    if (!resultBool)
        Log.error(TAG_LOG, "Wrong requestRouteToHost result: expected true, but was false");

    return resultBool;
}

In pratica, se non lo e’ gia’, viene attivata la connessione mobile grazie alla chiamata
startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, “enableHIPRI”)
per poi creare una route all’indirizzo da raggiungere che forza il passaggio sulla connessione mobile
requestRouteToHost(ConnectivityManager.TYPE_MOBILE_HIPRI, hostAddress)

Continue reading ‘Forzare l’uso della connessione mobile (3G o GPRS) con il WiFi in Android’ »

Pinch zoom in Android

La possibilita’ di zoomare e muovere le immagini usando gesture delle dita, il cosiddetto pinch-zoom, era una particolarita’ introdotta e supportata agli inizi solo dall’iPhone. Nel tempo, anche Android ha incluso questa feature, purtroppo pero’ senza api semplificate per implementarla nel proprio codice. Per fortuna, Luke Hutchison ha creato android-multitouch-controller, una libreria che permette di gestire la cosa abbastanza agevolmente, con supporto sia allo zoom, che al rotate. Licenziata sia sotto i termini dell’Apache License v2 e della GPL v2, puo’ essere usata in ogni progetto.

Personalmente ho fatto mio questo pezzo di codice, inglobandolo nelle Rainbowlibs e creando un controllo da usare bello che pronto nelle proprie applicazioni, la RainbowZoomableImageView, una specie di ImageView meno potente, che pero’ supporta il pitch-zoom out of the box, senza configurazioni aggiuntive.

Seguendo gli esempi proposti dalla libreria di Luke, ho creato la classe RainbowZoomableImageView.Img e la RainbowZoomableImageView che la utilizza, a cui ho aggiunto alcuni metodi di comodo, tra cui quelli per impostare l’immagine da visualizzare e per settare lo zoom manualmente, fondamentale per quei rari dispositivi che non hanno uno schermo o una versione di Android con il multitouch. Questi metodi sono assignImage(…) e incrementScale(…).

Come esempio di utilizzo di si puo’ buttare un occhio sull’activity ActImageFullScreen,java e sul relativo layout actimagefullscreen.xml di WebcamHolmes, un’altro progetto opensource che sto sviluppando.

La dichiarazione del layout e’ molto semplice, ed include solo il controllo personalizzato e quello per fare lo zoom










Mentre nell’activity, queste sono le parti salienti per gestire il tutto:

public class ActImageFullscreen extends Activity {

  private final static float ZOOM_INCREMENT = 0.1f;
  private static final int DIALOG_PROGRESS = 10;
  private static final int OPTIONMENU_SHOWHIDE_ZOOM = 10;
  private RainbowZoomableImageView mImage;
  private ActivityHelper mActivityHelper;
  private ZoomControls mZoomControls;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //make the view fullscreen
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
    setContentView(R.layout.actimagefullscreen);

    //load the image
    mImage = (RainbowZoomableImageView) findViewById(R.id.actimagefullscreen_imgMain);
    mImage.setBackgroundColor(Color.BLACK);
    //choose how to obtain the bitmap with the image
    Bitmap bitmap = ... //BitmapFactory.decodeStream(fis);
    mImage.assignImage(getResources(), bitmap);

    //link zoom press with image in/out zoom
    mZoomControls = (ZoomControls) findViewById(R.id.actimagefullscreen_zoomcontrols);
    mZoomControls.setOnZoomInClickListener(mOnZoomInClickListener);
    mZoomControls.setOnZoomOutClickListener(mOnZoomOutClickListener);
  }

  @Override
  protected void onPause() {
    mImage.onPause();
    super.onPause();
  }

  @Override
  protected void onResume() {
    super.onResume();
    mImage.onResume(getResources());
  }

  private OnClickListener mOnZoomInClickListener = new OnClickListener() {
    public void onClick(View v) {
      mImage.incrementScale(ZOOM_INCREMENT);
    }
  };

  private OnClickListener mOnZoomOutClickListener = new OnClickListener() {
    public void onClick(View v) {
      mImage.incrementScale(-ZOOM_INCREMENT);
    }
  };
}

Facile, no? ;)

android.app.Application, static singleton per variabili globali e NullPointerException

Leggendo l’Android reference guide a proposito di android.app.Application, troviamo scritto:

Base class for those who need to maintain global application state. You can provide your own implementation by specifying its name in your AndroidManifest.xml’s <application> tag, which will cause that class to be instantiated for you when the process for your application/package is created.
There is normally no need to subclass Application. In most situation, static singletons can provide the same functionality in a more modular way.

Sembra quindi il posto giusto, e piu’ elegante di un singleton, per mettere le nostre variabili globali quando non occorre fare cose troppo complicate e per inizializzarle al primo avvio dell’applicazione, mediante override del metodo OnCreate() della classe base. Qualcosa del genere:

La classe App.java

package it.rainbowbreeze.singleton;

public class App extends Application {
public static int myGlobalStaticValue;
public int myGlobalValue;

  @Override
  public void onCreate() {
    super.onCreate();
    myGlobalStaticValue = 10;
    myGlobalValue = 20;
  }
}

La modifica all’AndroidManifest.xml


E la chiamata da fare in un metodo qualunque di un’Activity, ad esempio:

//access to static field
int value = App.myGlobalStaticValue;
//access to non static fields
int value = ((App)context.getApplication()).myGlobalValue;

Da principio tutto funzionava, poi pero’ mi sono accorto di un problema non banale: che di tanto in tanto le variabili persistite in questo modo non mantengono il loro valore, soprattutto dopo che l’applicazione rimane in background per parecchio tempo o si fanno nel frattempo diverse altre cose col dispositivo.

Basito da questo comportamento, mi sono messo a cercare in giro, ed ecco che escono fuori le prime conferme, di cui riporto un estratto:

Consider a case – your app goes into the background because somebody is calling you (Phone app is in the foreground now). In this case && under some other conditions (check the above link for what they could be) the OS may kill your application process, including the Application subclass instance. As a result the state is lost. When you later return to the application, then the OS will restore its activity stack and Application subclass instance, however the myState field will be null.

This was an unplesant surprise for us in production. Believe me Android kills processes, it just depends on RAM state and other factors described in the documentation. It was a nightmare for us so I just share my real experience. Well we did not have this on emulators, but in real world some devices are ‘overloaded’ with apps, so killing a background process is a normal situation. Yes, if user then decides to get the app up into foreground – the OS restores its stack including the Application instance, however there will not be your static data you count on unless you persisted it.

In pratica, anche il contenuto dell’Application, come quello di ogni altro static singleton, puo’ venire ripulito dal sistema operativo. Come fare quindi per ovviare a questo problema?

Continue reading ‘android.app.Application, static singleton per variabili globali e NullPointerException’ »

adb devices, ???????? no permissions e come risolverlo

Come nel caso del Nexus One, puo’ capitare che sotto Linux l’ADB non riesca a collegarsi correttamente ad un nuovo smartphone Android, mostrando un malinconico messaggio quando viene lanciato il comando adb devices.
List of devices attached
????????????     no permissions

Esiste un modo per risolvere completamente il problema, che permette di capire qual’e’ il giusto idVendor da aggiundere al file .rules di udev, indipendentemente dal device in uso.

per prima cosa, occorre lanciare il comando
lsusb -v | grep idVendor

l’output restituito potrebbe essere il seguente:
idVendor           0x1d6b Linux Foundation
idVendor           0x045e Microsoft Corp.
idVendor           0x1d6b Linux Foundation
idVendor           0x1d6b Linux Foundation
idVendor           0x05c6 Qualcomm, Inc.
idVendor           0×5986 Acer, Inc

Ora, staccando il device e rilanciando il comando, ho visto che la riga che cambia e’ la seguente
idVendor           0x05c6 Qualcomm, Inc.
che contiene quindi l’idVendor che mi serve per configurare udev.

Continue reading ‘adb devices, ???????? no permissions e come risolverlo’ »

Statistiche sulla diffusione di Android e sulla risoluzione dei display

Uno dei principali problemi della piattaforma Android e’ la sua frammentarieta’: ci sono diverse versioni di sistema operativo in circolazione, ognuna con feature specifiche e anche la risoluzione del display tende a cambiare da una classe di dispositivi all’altra. Di seguito riporto alcune statistiche dei programmi che ho sviluppato, ora che hanno superato le 10mila installazioni e quindi iniziano a diventare un campione significativo.

Una precisazione. Non vengono consiredate tutte le versioni di Android inferiori alla 1.6 (quindi dalla 1.5 in giu’), dato che i miei programmi non la supportano e quindi non ho statistiche di questo tipo.

Innanzitutto iniziamo con la diffusione del sistema operativo:

Come si puo’ vedere, la versione attualmente piu’ diffusa e’ la 2.1, ma anche la 1.6 si difende bene, con conseguente disperazione di noi programmatori. Questo principalmente a causa dei produttori di device che non rilasciano aggiornamenti alla piattaforma, ma piuttosto forzano all’acquisto di un nuovo dispositivo dopo neanche un paio d’anni di utilizzo del vecchio, se si vuole restare al passo con i tempi.

Passiamo poi alla risoluzione dello schermo:

Continue reading ‘Statistiche sulla diffusione di Android e sulla risoluzione dei display’ »

TextView con link ad URL

Ci sono diversi modi per creare una TextView contente una URL cliccabile in Android. Vediamone alcuni.

Aggiungere alla TextView l’attributo android:autoLink=”web”. Se il testo contenuto al suo interno e’ riconducibile ad un indirizzo web, questo verra’ automaticamente mostrato in blu con la sottolineatura tipica di un hyperlink e se ci si clicca sopra verr’a aperta la URL corrispondende nel browser. Seguono alcuni esempi.

<TextView
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="www.rainbowbreeze.it"
  android:autoLink="web"
/>
<TextView
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="http://www.rainbowbreeze.it"
  android:autoLink="web"
/>

Se invece si vuole ottenere un effetto piu’ complesso, tipo una parola all’intero di una frase che, se cliccata, conduce ad un sito web, allora occorre inserire un po’ di codice, oltre all’xml. Partiamo definendo la TextView

Continue reading ‘TextView con link ad URL’ »