miércoles, 21 de noviembre de 2012

Encriptar/Desencriptar datos en 4.2

En algunas ocasiones nos puede interesar encriptar datos para aumentar la seguridad de nuestra aplicación. Para ello podemos usar la siguiente clase que funciona hasta la versión 4.1.2 de Android:

/**
 * Util class to perform encryption/decryption over strings.
 */
public final class UtilsEncryption
{
    /** The logging TAG */
    private static final String TAG = UtilsEncryption.class.getName();

    /** */
    private static final String KEY = "some_encryption_key";

    /**
     * Avoid instantiation. <br/>
     */
    private UtilsEncryption()
    {
    }

    /** The HEX characters */
    private final static String HEX = "0123456789ABCDEF";

    /**
     * Encrypt a given string. <br/>
     * 
     * @param the string to encrypt
     * @return the encrypted string in HEX
     */
    public static String encrypt( String cleartext )
    {
        try
        {
            byte[] result = process( Cipher.ENCRYPT_MODE, cleartext.getBytes() );
            return toHex( result );
        }
        catch ( Exception e )
        {
            System.out.println( TAG + ":encrypt:" + e.getMessage() );
        }
        return null;
    }

    /**
     * Decrypt a HEX encrypted string. <br/>
     * 
     * @param the HEX string to decrypt
     * @return the decrypted string
     */
    public static String decrypt( String encrypted )
    {
        try
        {
            byte[] enc = fromHex( encrypted );
            byte[] result = process( Cipher.DECRYPT_MODE, enc );
            return new String( result );
        }
        catch ( Exception e )
        {
            System.out.println( TAG + ":decrypt:" + e.getMessage() );
        }
        return null;
    }


    /**
     * Get the raw encryption key. <br/>
     * 
     * @param the seed key
     * @return the raw key
     * @throws NoSuchAlgorithmException
     */
    private static byte[] getRawKey()
        throws NoSuchAlgorithmException
    {
        KeyGenerator kgen = KeyGenerator.getInstance( "AES" );
        SecureRandom sr = SecureRandom.getInstance( "SHA1PRNG" );
        sr.setSeed( KEY.getBytes() );
        kgen.init( 128, sr );
        SecretKey skey = kgen.generateKey();
        return skey.getEncoded();
    }

    /**
     * Process the given input with the provided mode. <br/>
     * 
     * @param the cipher mode
     * @param the value to process
     * @return the processed value as byte[]
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     */
    private static byte[] process( int mode, byte[] value )
        throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException,     NoSuchAlgorithmException,
        NoSuchPaddingException
    {
        SecretKeySpec skeySpec = new SecretKeySpec( getRawKey(), "AES" );
        Cipher cipher = Cipher.getInstance( "AES" );
        cipher.init( mode, skeySpec );
        byte[] encrypted = cipher.doFinal( value );
        return encrypted;
    }

    /**
     * Decode an HEX encoded string into a byte[]. <br/>
     * 
     * @param the HEX string value
     * @return the decoded byte[]
     */
    protected static byte[] fromHex( String value )
    {
        int len = value.length() / 2;
        byte[] result = new byte[len];
        for ( int i = 0; i < len; i++ )
        {
            result[i] = Integer.valueOf( value.substring( 2 * i, 2 * i + 2 ), 16     ).byteValue();
        }
        return result;
    }

    /**
     * Encode a byte[] into an HEX string. <br/>
     * 
     * @param the byte[] value
     * @return the HEX encoded string
     */
    protected static String toHex( byte[] value )
    {
        if ( value == null )
        {
            return "";
        }
        StringBuffer result = new StringBuffer( 2 * value.length );
        for ( int i = 0; i < value.length; i++ )
        {
            byte b = value[i];

            result.append( HEX.charAt( ( b >> 4 ) & 0x0f ) );
            result.append( HEX.charAt( b & 0x0f ) );
        }
        return result.toString();
    }
}

Para qué la clase funcione a partir de la 4.2 tenemos que sustituir el siguiente código de la clase anterior:

     SecureRandom sr = SecureRandom.getInstance( "SHA1PRNG", "Crypto" );

Este problema es debido a los providers que usa Google en la 4.2. Hasta la 4.1.2 se usa Crypto ya que si hacemos ran.getProvider podemos verlo. A partir de la 4.2 si ejecutamos la misma instrucción nos devuelve AndroidOpenSSL.

Espero que les haya servido.

It works!
Roger Sala,



sábado, 10 de noviembre de 2012

StrictMode

El StrictMode nos permite analizar nuestra aplicación en tiempo de ejecución. Esta disponible a partir de la API 9. Con su uso podemos detectar:
  • Escrituras/lecturas en disco.
  • Uso de la red
  • Violación: log/crash, dropbox, cuadros de diálogos que pueden perjudicar el uso de la aplicación (que sea molesto visitar nuestra app por culpa de los cuadros de dialógos).
A nadie le gusta descargarse una aplicación y tener que esperar 7 segundos para poder realizar una primera acción. A nadie le gusta tener un Progress Dialog y no poder usar la aplicación a su vez. StrictMode, nos ayuda a detectar estos casos y así, podemos mejorar el uso de nuestra aplicación.
Si quieren leer más sobre StrictMode aquí les dejo un post de Android Developers.

A continuación el código a escribir para usar StrictMode:

1.- Con la configuración por defecto:

public void onCreate(Bundle savedInstanceState) {
        if (DEVELOPER_MODE) {
        StrictMode.enableDefaults();
        }
        super.onCreate(savedInstanceState);
}

2.- Con la configuración personalizada:

public void onCreate(Bundle savedInstanceState) {
        if (DEVELOPER_MODE) {
              StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
             .detectDiskReads()
             .detectDiskWrites()
             .detectNetwork()
             .penaltyLog()
             .build());

        }
        super.onCreate(savedInstanceState);
}

IMPORTANTE: Usarlo solo cuando esten debugando o provando la aplicación. Deshabilitarlo para subirla en el Store. Para eso se usa la variable DEVELOPER_MODE.



lunes, 5 de noviembre de 2012

Custom Loading

Si deseamos personalizar los elementos de cargando de nuestra aplicación podemos tan solo tenemos que crear nuestro propio diseño y sobreescribir el de Android. En este post vamos a ver como se realiza dicha acción: El diseño esta hecho a partir de flechas pero cada uno puedo poner lo que desee y el número de elementos que prefiera (aquí solo hay cuatro pero se pueden poner más o menos).

A continuación el código:
res/anim/custom_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item
        android:drawable="@drawable/right"
        android:duration="150"/>
    <item
        android:drawable="@drawable/up"
        android:duration="150"/>
    <item
        android:drawable="@drawable/left"
        android:duration="150"/>
    <item
        android:drawable="@drawable/down"
        android:duration="150"/>
</animation-list>

activity_loading.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

</RelativeLayout>




LoadingActivity.java

package com.example.blogcustomdialoanimation;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.view.Menu;

public class LoadingActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_loading);
        
        ProgressDialog dialog = new ProgressDialog(this);
        dialog.setIndeterminate(true);
        dialog.setIndeterminateDrawable(getResources().getDrawable(R.anim.custom_dialog));
        dialog.setMessage("Some Text");
        dialog.show();
        
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_loading, menu);
        return true;
    }
}

A continuación les dejo las imagenes:




It works!
Roger Sala,

jueves, 1 de noviembre de 2012

ViewFlipper estático y dinámico

Este Widget nos permite cambiar de una vista a otra de forma automática y, si se desea, aplicando animaciones. Por ejemplo:
- Podemos crear un marco de foto digital que cada X segundos vayan cambiando las fotos.
- Podemos añadir nuestra propia publicidad y que vaya cambiando el anuncio cada X segundos.
- Y otros...

En estes post vamos a ver como añadir en nuestro proyecto un ViewFlipper, ya sea con contenido estático, es decir, en el layout definimos el número máximo de elementos que puede tener. O bien, con contenido dinámico, es decir, en nuestra Activity le vamos añadiendo vistas.
Antes de empezar, destacar, que para el uso de un ViewFlipper se recomienda un mínimo de 2 vistas (layout, imageview, textview, etc.). Así pues, aquí les dejo el código:

res/anim/push_in_left.xml

<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright (C) 2007 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at
  
          http://www.apache.org/licenses/LICENSE-2.0
  
     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<set xmlns:android="http://schemas.android.com/apk/res/android" >

    <translate
        android:duration="700"
        android:fromXDelta="100%p"
        android:toXDelta="0" />

    <alpha
        android:duration="700"
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />

</set>


res/anim/push_out_left.xml

<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright (C) 2007 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at
  
          http://www.apache.org/licenses/LICENSE-2.0
  
     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<set xmlns:android="http://schemas.android.com/apk/res/android" >

    <translate
        android:duration="700"
        android:fromXDelta="0"
        android:toXDelta="-100%p" />

    <alpha
        android:duration="700"
        android:fromAlpha="1.0"
        android:toAlpha="0.0" />

</set>


activity_flipper.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/linear_layout_highlighted"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:orientation="vertical" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="20dip"
        android:text="Static Flipper with push animation" />

    <ViewFlipper
        android:id="@+id/flipper_static"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dip"
        android:flipInterval="2000" >

        <ImageView
            android:id="@+id/imageview_highlighted_1"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:scaleType="fitCenter"
            android:src="@drawable/android_one" />

        <ImageView
            android:id="@+id/imageview_highlighted_2"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:scaleType="fitCenter"
            android:src="@drawable/android_two" />
    </ViewFlipper>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="20dip"
        android:text="Dynamic Flipper" />

    <ViewFlipper
        android:id="@+id/flipper_dynamic"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dip"
        android:flipInterval="4000" >
    </ViewFlipper>

</LinearLayout>

FlipperActivity.java
package com.example.blogviewflipper;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.ViewFlipper;

public class FlipperActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_flipper);

// static flipper
ViewFlipper mStaticFlipper = ((ViewFlipper) findViewById(R.id.flipper_static));
mStaticFlipper.startFlipping();
mStaticFlipper.setInAnimation(AnimationUtils.loadAnimation(this,
R.anim.push_in_left));
mStaticFlipper.setOutAnimation(AnimationUtils.loadAnimation(this,
R.anim.push_out_left));

// dynamic flipper
ViewFlipper mDynamicFlipper = ((ViewFlipper) findViewById(R.id.flipper_dynamic));

ImageView image = new ImageView(this);
image.setImageResource(R.drawable.android_one);
image.setScaleType(ScaleType.FIT_CENTER);
mDynamicFlipper.addView(image);

image = new ImageView(this);
image.setImageResource(R.drawable.android_two);
image.setScaleType(ScaleType.FIT_CENTER);
mDynamicFlipper.addView(image);

image = new ImageView(this);
image.setImageResource(R.drawable.android_three);
image.setScaleType(ScaleType.FIT_CENTER);
mDynamicFlipper.addView(image);

mDynamicFlipper.startFlipping();
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_flipper, menu);
return true;
}

}

A continuación les dejo una imagen, dónde se muestra el contenido:



It works!
Roger Sala,

domingo, 28 de octubre de 2012

Reconocimiento de Voz

Si en nuestra aplicación es necesario un buscador web, de aplicaciones o similares, podemos usar la opción de búsqueda por voz. Este tipo de búsqueda se puede configurar para que sea interna al teléfono, por ejemplo aplicaciones, o bien, en la web.

En el siguiente ejemplo veremos que está configurado para web y que el número máximo de elementos que se van a mostrar en la lista son 10. Esta configuración se puede hacer dinámica: en la búsqueda por web o aplicaciones se puede realizar mediante la inserción de un RadioButton en el código. En el segundo caso podemos poner un contador, un spinner con el número de búsquedas limitadas...

A continuación les dejo el código:

AndroidManifest.xml

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

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

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

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".VoiceRecognitionActivity"
            android:label="@string/title_activity_voice_recognition" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

activity_voice_recognition.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <EditText
        android:id="@+id/etTextHint"
        android:gravity="top"
        android:inputType="textMultiLine"
        android:lines="1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Speech hint here"/>

    <Button
        android:id="@+id/btSpeak"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="speak"
        android:padding="@dimen/padding_medium"
        android:text="Speak"
        tools:context=".VoiceRecognitionActivity" />

    <ListView
        android:id="@+id/lvTextMatches"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

VoiceRecognitionActivity.java
package com.example.blogvoicerecognition;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.app.SearchManager;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;

public class VoiceRecognitionActivity extends Activity {

private static final int VOICE_RECOGNITION_REQUEST_CODE = 0;
private static final int MATCHES = 10;

private EditText mEditText;
private ListView mListViewResults;
private Button mButtonSpeak;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_voice_recognition);
mEditText = (EditText) findViewById(R.id.etTextHint);
mListViewResults = (ListView) findViewById(R.id.lvTextMatches);
mButtonSpeak = (Button) findViewById(R.id.btSpeak);
voiceRecognition();
}

public void voiceRecognition() {
// Check if voice recognition is present
PackageManager pm = getPackageManager();
List<ResolveInfo> activities = pm.queryIntentActivities(new Intent(
RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);
if (activities.size() == 0) {
mButtonSpeak.setEnabled(false);
mButtonSpeak.setText("Voice recognizer not present");
Toast.makeText(this, "Voice recognizer not present",
Toast.LENGTH_SHORT).show();
}
}

public void speak(View view) {
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);

// Specify the calling package to identify your application
intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, getClass()
.getPackage().getName());

// Display an hint to the user about what he should say.
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, mEditText.getText()
.toString());

// Given an hint to the recognizer about what the user is going to say
// There are two form of language model available
// 1.LANGUAGE_MODEL_WEB_SEARCH : For short phrases
// 2.LANGUAGE_MODEL_FREE_FORM : If not sure about the words or phrases
// and its domain.
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);

// Specify how many results you want to receive. The results will be
// sorted where the first result is the one with higher confidence.
intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, MATCHES);
// Start the Voice recognizer activity for the result.
startActivityForResult(intent, VOICE_RECOGNITION_REQUEST_CODE);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == VOICE_RECOGNITION_REQUEST_CODE)

// If Voice recognition is successful then it returns RESULT_OK
if (resultCode == RESULT_OK) {

ArrayList<String> textMatchList = data
.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);

if (!textMatchList.isEmpty()) {
// If first Match contains the 'search' word
// Then start web search.
if (textMatchList.get(0).contains("search")) {

String searchQuery = textMatchList.get(0);
searchQuery = searchQuery.replace("search", "");
Intent search = new Intent(Intent.ACTION_WEB_SEARCH);
search.putExtra(SearchManager.QUERY, searchQuery);
startActivity(search);
} else {
// populate the Matches
mListViewResults.setAdapter(new ArrayAdapter<String>(
this, android.R.layout.simple_list_item_1,
textMatchList));
}

}
// Result code for various error.
} else if (resultCode == RecognizerIntent.RESULT_AUDIO_ERROR) {
showToastMessage("Audio Error");
} else if (resultCode == RecognizerIntent.RESULT_CLIENT_ERROR) {
showToastMessage("Client Error");
} else if (resultCode == RecognizerIntent.RESULT_NETWORK_ERROR) {
showToastMessage("Network Error");
} else if (resultCode == RecognizerIntent.RESULT_NO_MATCH) {
showToastMessage("No Match");
} else if (resultCode == RecognizerIntent.RESULT_SERVER_ERROR) {
showToastMessage("Server Error");
}
super.onActivityResult(requestCode, resultCode, data);
}

/**
* Helper method to show the toast message
**/
void showToastMessage(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
}

A continuación les dejo la imagen de la búsqueda de la palabra "Android":


It works!
Roger Sala,



viernes, 26 de octubre de 2012

ActionBarSherlock: Usar, personalizar y Tabs

ActionBarSherlock es un proyecto que cualquier desarrollador puede descargar e integrar en sus proyectos. Para saber exactamente que es y que posibilidades tiene mejor consultarlo en la web. En este post nos vamos a centrar en como usar la ActionBar que ofrece.
Cómo hacerlo es muy simple:
1.- Descargar el proyecto de la web. ZIP
2.- Descomprimir...dentro de la carpeta creada existe la carpeta "library". Este carpeta es la que tenemos que importar a nuestro workspace de Eclipse.

Una vez importado el proyecto tenemos que crear un nuevo proyecto e indicarle que vamos usar el "library" como librería. Pasos:
1.- Crear proyecto.
2.- Una vez creado...Clic derecho sobre el proyecto --> Properties --> Android --> (A la parte inferior exite la opción de añadir librería) Add --> Seleccionamos el "library".

Una vez importado ya podemos usarlo. Para ello tenemos el siguiente código:

res/menu/activity_main.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@+id/menu_settings"
        android:orderInCategory="100"
        android:showAsAction="ifRoom"
        android:title="@string/menu_settings">
        <menu>
            <item
                android:id="@+id/form1"
                android:title="Form 1"/>
            <item
                android:id="@+id/form2"
                android:title="Form 2"/>
        </menu>
    </item>
    <item
        android:id="@+id/menu_map"
        android:orderInCategory="200"
        android:showAsAction="ifRoom"
        android:title="map">
    </item>

</menu>


res/values/styles.xml

<resources>
    <style name="AppTheme" parent="android:Theme.Light" />
<!-- Usaremos nuestro propio estilo sino-->
    <style name="Theme.Styled" parent="Theme.Sherlock.Light.DarkActionBar">
        <item name="actionBarStyle">@style/Widget.Styled.ActionBar</item>
        <item name="android:actionBarStyle">@style/Widget.Styled.ActionBar</item>
    </style>
<!-- Sobrescribimos la actionBarSherlock con nuestro propio background-->
    <style name="Widget.Styled.ActionBar" parent="Widget.Sherlock.Light.ActionBar.Solid.Inverse">
        <item name="android:background">@drawable/comun_actionbar</item>
    </style>
</resources>


//Como podemos ver el Theme.Styled es custom y así nos permite personalizar el estilo de la ActionBar. NOTA: Si no lo personalizamos ponemos :Theme.Sherlock
AndroidManifest.xml

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

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

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.Styled"
        <activity
            android:name=".ChooserActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".SingleActivity"/>
        <activity android:name=".TabsActivity" android:label="TabsView" />
    </application>
</manifest>

activity_chooser.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/button_single"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="40dip"
        android:gravity="center_horizontal"
        android:padding="@dimen/padding_medium"
        android:text="Single"
        tools:context=".MainActivity" />

    <Button
        android:id="@+id/button_tabs"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:gravity="center_horizontal"
        android:padding="@dimen/padding_medium"
        android:text="Tabs"
        tools:context=".MainActivity" />

</LinearLayout>

//Clase inicial (indice de la aplicación) que nos permite seleccionar entre una vista simple o bien la de los Tabs. 
ChooserActivity.java
package com.example.blogactionbarsherlock;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.actionbarsherlock.app.SherlockActivity;

public class ChooserActivity extends SherlockActivity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chooser);

((Button) findViewById(R.id.button_single))
.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
startActivity(new Intent(ChooserActivity.this, SingleActivity.class));
}
});

((Button) findViewById(R.id.button_tabs))
.setOnClickListener(new View.OnClickListener() {

@Override
public void onClick(View v) {
startActivity(new Intent(ChooserActivity.this, TabsActivity.class));
}
});

}

}

activity_single.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
</RelativeLayout>


SingleActivity.java
package com.example.blogactionbarsherlock;

import android.os.Bundle;
import android.widget.Toast;

import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockActivity;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;

public class SingleActivity extends SherlockActivity {

private Menu mMenu;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single);

// Customize
ActionBar actionBar = getSupportActionBar();
actionBar.setBackgroundDrawable(getResources().getDrawable(
R.drawable.comun_background_grey));
actionBar.setTitle("Custom Title");
actionBar.setDisplayShowHomeEnabled(false);

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {

MenuInflater menuInflater = getSupportMenuInflater();
menuInflater.inflate(R.menu.activity_main, menu);

// Manage Menu - Customize
mMenu = menu;
mMenu.findItem(R.id.menu_map).setVisible(false);

return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_settings:
Toast.makeText(this, "Settings", Toast.LENGTH_SHORT).show();
break;

default:
break;
}

return true;
}
}

tabs_layout.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView android:id="@+id/textView_tab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</RelativeLayout>


TabsActivity.java
package com.example.blogactionbarsherlock;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.widget.Toast;

import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.ActionBar.Tab;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;

public class TabsActivity extends SherlockFragmentActivity implements
ActionBar.TabListener {

private Menu mMenu;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single);

ActionBar actionBar = getSupportActionBar();

// Navigation
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

// create tabs
ActionBar.Tab newTab0 = getSupportActionBar().newTab();
newTab0.setText("Tab 0");
newTab0.setTag("0");
ActionBar.Tab newTab1 = getSupportActionBar().newTab();
newTab1.setText("Tab 1");
newTab1.setTag("1");

// Listeners
newTab0.setTabListener(this);
newTab1.setTabListener(this);

// add tabs
getSupportActionBar().addTab(newTab0);
getSupportActionBar().addTab(newTab1);

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {

MenuInflater menuInflater = getSupportMenuInflater();
menuInflater.inflate(R.menu.activity_main, menu);

// Manage Menu
mMenu = menu;
mMenu.findItem(R.id.menu_settings).setVisible(false);

return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_settings:
Toast.makeText(this, "Settings", Toast.LENGTH_SHORT).show();
break;

default:
break;
}

return true;
}

@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
                
                //Añadimos el fragment en el tab que queremos. En este ejemplo añadimos el mismo.
Fragment ef = (Fragment) new ExampleFragment();
FragmentManager fragMgr = getSupportFragmentManager();
ft = fragMgr.beginTransaction();

switch (Integer.valueOf((String) tab.getTag())) {
case 0:
Log.d("SimpleActionBarTabsActivity",
"tab " + String.valueOf(tab.getPosition()) + " 0 clicked");
ft.add(android.R.id.content, ef, null);
break;
case 1:
Log.d("SimpleActionBarTabsActivity",
"tab " + String.valueOf(tab.getPosition()) + " 1 clicked");
ft.add(android.R.id.content, ef, null);
break;
}

ft.commit();
Log.d("SimpleActionBarTabsActivity",
"tab " + String.valueOf(tab.getPosition()) + " clicked");
}

@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
// switch (Integer.valueOf((String) tab.getTag())) {
// case 0:
// Log.d("SimpleActionBarTabsActivity",
// "tab " + String.valueOf(tab.getPosition())
// + " 0 un-clicked");
// break;
// case 1:
// Log.d("SimpleActionBarTabsActivity",
// "tab " + String.valueOf(tab.getPosition())
// + " 1 un-clicked");
// break;
// }
// Log.d("SimpleActionBarTabsActivity",
// "tab " + String.valueOf(tab.getPosition()) + " un-clicked");

}

@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// switch (Integer.valueOf((String) tab.getTag())) {
// case 0:
// Log.d("SimpleActionBarTabsActivity",
// "tab " + String.valueOf(tab.getPosition())
// + " 0 re-clicked");
// break;
// case 1:
// Log.d("SimpleActionBarTabsActivity",
// "tab " + String.valueOf(tab.getPosition())
// + " 1 re-clicked");
// break;
// }
// Log.d("SimpleActionBarTabsActivity",
// "tab " + String.valueOf(tab.getPosition()) + " re-clicked");

}
}

fragment_example.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView android:id="@+id/textView_tab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</RelativeLayout>

ExampleFragment.java

package com.example.blogactionbarsherlock;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.actionbarsherlock.app.SherlockFragment;

public class ExampleFragment extends SherlockFragment {

public ExampleFragment(){
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_example, container, false);
((TextView) view.findViewById(R.id.textView_tab)).setText("Custom tab");
return view;
}

}


A continuación las capturas de pantalla de la aplicación que podemos ver:

Chooser




SingleActivity + CustomActionBar + Menú




TabsActivity + ActionBar en Theme.Styled



It works!
Roger Sala