Author Archives: admin

Android:RSS Parser

[Fuente: http://devtuts.mobi/Android:RSS_Parser]

Introduccion

En este tuto, intentaremos mostrar como leer un RSS en Android. Pasaremos por encima sin ir a los detalles.

El ejemplo intenta imprimir los titulos de las noticias proporcionados por http://news.google.fr/?output=rss.

Para mostrarlo , simplemente utilizamos una lista.

Creamos un nuevo proyecto, con un ListActivity. Podemos añadir e inicializar atributos para la url del feed y la lista:

package com.jes.rssparser;

import java.util.ArrayList;

import android.app.ListActivity;
import android.os.Bundle;

public class RSSParserActivity extends ListActivity {
	private final String feeds = "http://news.google.fr/?output=rss";
	private ArrayList<String> list;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		list = new ArrayList<String>();
	}
}

Utilizaremos los objetos RootElement y Element para definir la estructura de nuestro feeds. Es muy simple, solo para indicar quien es el padre/hijo de quien:

		RootElement root = new RootElement("rss");
		Element itemlist = root.getChild("channel");
		Element item =  itemlist.getChild("item");

Los elementos item corresponden a cada sección del article. Cada item tiene a su vez varios hijos: titulo, link, descripcion, etc. Por ejemplo sacaremos solo el titulo pero es lo mismo para las otras tags.

Para recuperar los contenidos de la tag title, utilizaremos el método setEndTextElementListener y definiremos un listener:

		item.getChild("title").setEndTextElementListener(
				new EndTextElementListener() {
					public void end(String body) {
						list.add(body);
					}
				});
“body” es el contenido que buscamos, asi que lo añado a mi lista. Finalmente, debemos abrir la conexion, recuperar el contenido parsear el rss:
		try {
			url = new URL(feeds);
			InputStream input = url.openConnection().getInputStream();
			Xml.parse(input, Xml.Encoding.UTF_8, root.getContentHandler());
		} catch (Exception e) {
			Log.e("RSSParser", e.toString());
		}

For details, we instantiate a URL, then start the connection with openconnection(). Data are retrieved with getInputStream (), which is placed in the variable input. Xml.parse() parses the variable input, with the structure that we have stated previously, in a UTF-8 encoding.

That’s all! It remains to link the variable list with the ListView that is showing. Finally, we get the following code:

  1. public class Main extends ListActivity {
  2. private final String feeds = “http://news.google.fr/?output=rss”;
  3. private URL url;
  4. private ArrayList<String> list;
  5. /** Called when the activity is first created. */
  6. @Override
  7. public void onCreate(Bundle savedInstanceState) {
  8. super.onCreate(savedInstanceState);
  9. list = new ArrayList<String>();
  10. RootElement root = new RootElement(“rss”);
  11. Element itemlist = root.getChild(“channel”);
  12. Element item =  itemlist.getChild(“item”);
  13. item.getChild(“title”).setEndTextElementListener(new EndTextElementListener(){
  14. public void end(String body) {
  15. list.add(body);
  16. }
  17. });
  18. try {
  19. url = new URL(feeds);
  20. InputStream input = url.openConnection().getInputStream();
  21. Xml.parse(input, Xml.Encoding.UTF_8, root.getContentHandler());
  22. } catch (Exception e) {
  23. Log.e(“RSSParser”,e.toString());
  24. }
  25. setListAdapter(new ArrayAdapter<String>(this, R.layout.item, list));
  26. }
  27. }

Note

Remember that RSS is an XML file. So you know also parse an XML file!

 

Android: App Widgets

  • Los Widgets proporcionan a los usuarios acceso a algunas caracteristicas de una aplicación directamente desde la pantalla Home del terminal (sin la necesidad de lanzar una activity)
  • Los Widgets son implementados por un tipo especial de broadcast receiver que maneja el ciclo de vida de un App Widget.

Clases principales

  1. AppWidgetProvider
  2. AppWidgetProviderInfo
  3. AppWidgetManager
  4. Ver también:
    1. Guia de diseño de un widget –> App Widget Design Guidelines
    2. Introducción a los widgets de pantalla principal en el framework AppWidget –>Introducing home screen widgets and the AppWidget framework »

Las App Widgets son vistas de las aplicaciones en miniatura que pueden ser embebidas en otras aplicaciones (como por ejemplo la pantalla Home) y recibir actualizaciones periodicas. Estas vistas se refieren a Widgets en el interfaz de usuario, y puedes publicar uno con un App Widget provider. Al componente de la aplicación que es capaz de contener otros App Widgets se le llama App Widget host. La siguiente imagen muestra un Widget de un reproductor de música:

En este tuto veremos como publicar un Widget utilizando un App widget provider.

Conceptos básicos

Para crear un widget , necesitamos lo siguiente:

  • Un objeto AppWidgetProviderInfo_ Este objeto describe los metadatos de un Widget, tales como el layout del widget, frecuencia de actualización, y la clase AppWidgetProvider. Todo esto se define en un fichero XML.
  • Una implementación de la clase AppWidgetProvider. Aqui definimos los métodos básicos que permiten comunicarse por interfaz de programación con el widget, basados en eventos de broadcast. A través de ellos, recibirás broadcast cuando el widget se actualice, se habilite, se deshabilite o sea borrado.
  • Un View layout para definir el layout inicial del Widget , definido en el XML.

Además, puedes implementar una activity de configuración del Widget. Esta es una Activity opcional que se lanza cuando el usuario añade tu Widget y permite modificar los settings del widget en tiempo de creación.

A continuación vemos cómo configurar estos componentes:

Declaring an App Widget in the Manifest

First, declare the AppWidgetProvider class in your application’s AndroidManifest.xml file. For example:

<receiver android:name="ExampleAppWidgetProvider" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
               android:resource="@xml/example_appwidget_info" />
</receiver>

The <receiver> element requires the android:name attribute, which specifies the AppWidgetProvider used by the App Widget.

The <intent-filter> element must include an <action> element with the android:name attribute. This attribute specifies that the AppWidgetProvider accepts theACTION_APPWIDGET_UPDATE broadcast. This is the only broadcast that you must explicitly declare. The AppWidgetManager automatically sends all other App Widget broadcasts to the AppWidgetProvider as necessary.

The <meta-data> element specifies the AppWidgetProviderInfo resource and requires the following attributes:

  • android:name – Specifies the metadata name. Use android.appwidget.provider to identify the data as the AppWidgetProviderInfo descriptor.
  • android:resource – Specifies the AppWidgetProviderInfo resource location.

Adding the AppWidgetProviderInfo Metadata

The AppWidgetProviderInfo defines the essential qualities of an App Widget, such as its minimum layout dimensions, its initial layout resource, how often to update the App Widget, and (optionally) a configuration Activity to launch at create-time. Define the AppWidgetProviderInfo object in an XML resource using a single <appwidget-provider> element and save it in the project’s res/xml/ folder.

For example:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dp"
    android:minHeight="72dp"
    android:updatePeriodMillis="86400000"
    android:previewImage="@drawable/preview"
    android:initialLayout="@layout/example_appwidget"
    android:configure="com.example.android.ExampleAppWidgetConfigure"
    android:resizeMode="horizontal|vertical">
</appwidget-provider>

Here’s a summary of the <appwidget-provider> attributes:

  • The values for the minWidth and minHeight attributes specify the minimum amount of space the App Widget consumes by default. The default Home screen positions App Widgets in its window based on a grid of cells that have a defined height and width. If the values for an App Widget’s minimum width or height don’t match the dimensions of the cells, then the App Widget dimensions round up to the nearest cell size.See the App Widget Design Guidelines for more information on sizing your App Widgets.Note: To make your app widget portable across devices, your app widget’s minimum size should never be larger than 4 x 4 cells.
  • The minResizeWidth and minResizeHeight attributes specify the App Widget’s absolute minimum size. These values should specify the size below which the App Widget would be illegible or otherwise unusable. Using these attributes allows the user to resize the widget to a size that may be smaller than the default widget size defined by theminWidth and minHeight attributes. Introduced in Android 3.1.See the App Widget Design Guidelines for more information on sizing your App Widgets.
  • The updatePeriodMillis attribute defines how often the App Widget framework should request an update from the AppWidgetProvider by calling the onUpdate() callback method. The actual update is not guaranteed to occur exactly on time with this value and we suggest updating as infrequently as possible—perhaps no more than once an hour to conserve the battery. You might also allow the user to adjust the frequency in a configuration—some people might want a stock ticker to update every 15 minutes, or maybe only four times a day.Note: If the device is asleep when it is time for an update (as defined by updatePeriodMillis), then the device will wake up in order to perform the update. If you don’t update more than once per hour, this probably won’t cause significant problems for the battery life. If, however, you need to update more frequently and/or you do not need to update while the device is asleep, then you can instead perform updates based on an alarm that will not wake the device. To do so, set an alarm with an Intent that your AppWidgetProvider receives, using the AlarmManager. Set the alarm type to either ELAPSED_REALTIME or RTC, which will only deliver the alarm when the device is awake. Then set updatePeriodMillis to zero ("0").
  • The initialLayout attribute points to the layout resource that defines the App Widget layout.
  • The configure attribute defines the Activity to launch when the user adds the App Widget, in order for him or her to configure App Widget properties. This is optional (readCreating an App Widget Configuration Activity below).
  • The previewImage attribute specifies a preview of what the app widget will look like after it’s configured, which the user sees when selecting the app widget. If not supplied, the user instead sees your application’s launcher icon. This field corresponds to the android:previewImage attribute in the <receiver> element in the AndroidManifest.xmlfile. For more discussion of using previewImage, see Setting a Preview Image. Introduced in Android 3.0.
  • The autoAdvanceViewId attribute specifies the view ID of the app widget subview that should be auto-advanced by the widget’s host. Introduced in Android 3.0.
  • The resizeMode attribute specifies the rules by which a widget can be resized. You use this attribute to make homescreen widgets resizeable—horizontally, vertically, or on both axes. Users touch-hold a widget to show its resize handles, then drag the horizontal and/or vertical handles to change the size on the layout grid. Values for the resizeModeattribute include “horizontal”, “vertical”, and “none”. To declare a widget as resizeable horizontally and vertically, supply the value “horizontal|vertical”. Introduced in Android 3.1.

See the AppWidgetProviderInfo class for more information on the attributes accepted by the <appwidget-provider> element.

Creating the App Widget Layout

You must define an initial layout for your App Widget in XML and save it in the project’s res/layout/ directory. You can design your App Widget using the View objects listed below, but before you begin designing your App Widget, please read and understand the App Widget Design Guidelines.

Creating the App Widget layout is simple if you’re familiar with XML Layouts. However, you must be aware that App Widget layouts are based on RemoteViews, which do not support every kind of layout or view widget.

A RemoteViews object (and, consequently, an App Widget) can support the following layout classes:

And the following widget classes:

Descendants of these classes are not supported.

Adding margins to App Widgets

Widgets should not generally extend to screen edges and should not visually be flush with other widgets, so you should add margins on all sides around your widget frame.

As of Android 4.0, app widgets are automatically given padding between the widget frame and the app widget’s bounding box to provide better alignment with other widgets and icons on the user’s home screen. To take advantage of this strongly recommended behavior, set your application’s targetSdkVersion to 14 or greater.

It’s easy to write a single layout that has custom margins applied for earlier versions of the platform, and has no extra margins for Android 4.0 and greater:

  1. Set your application’s targetSdkVersion to 14 or greater.
  2. Create a layout such as the one below, that references a dimension resource for its margins:
    <FrameLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:padding="@dimen/widget_margin">
    
      <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal"
        android:background="@drawable/my_widget_background">
        …
      </LinearLayout>
    
    </FrameLayout>
  3. Create two dimensions resources, one in res/values/ to provide the pre-Android 4.0 custom margins, and one in res/values-v14/ to provide no extra padding for Android 4.0 widgets:res/values/dimens.xml:
    <dimen name="widget_margin">8dp</dimen>

    res/values-v14/dimens.xml:

    <dimen name="widget_margin">0dp</dimen>

Another option is to simply build extra margins into your nine-patch background assets by default, and provide different nine-patches with no margins for API level 14 or later.

Using the AppWidgetProvider Class

You must declare your AppWidgetProvider class implementation as a broadcast receiver using the<receiver> element in the AndroidManifest (seeDeclaring an App Widget in the Manifest above).

The AppWidgetProvider class extends BroadcastReceiver as a convenience class to handle the App Widget broadcasts. The AppWidgetProvider receives only the event broadcasts that are relevant to the App Widget, such as when the App Widget is updated, deleted, enabled, and disabled. When these broadcast events occur, the AppWidgetProvider receives the following method calls:

onUpdate()
This is called to update the App Widget at intervals defined by the updatePeriodMillis attribute in the AppWidgetProviderInfo (see Adding the AppWidgetProviderInfo Metadata above). This method is also called when the user adds the App Widget, so it should perform the essential setup, such as define event handlers for Views and start a temporary Service, if necessary. However, if you have declared a configuration Activity, this method is not called when the user adds the App Widget, but is called for the subsequent updates. It is the responsibility of the configuration Activity to perform the first update when configuration is done. (See Creating an App Widget Configuration Activity below.)
onDeleted(Context, int[])
This is called every time an App Widget is deleted from the App Widget host.
onEnabled(Context)
This is called when an instance the App Widget is created for the first time. For example, if the user adds two instances of your App Widget, this is only called the first time. If you need to open a new database or perform other setup that only needs to occur once for all App Widget instances, then this is a good place to do it.
onDisabled(Context)
This is called when the last instance of your App Widget is deleted from the App Widget host. This is where you should clean up any work done in onEnabled(Context), such as delete a temporary database.
onReceive(Context, Intent)
This is called for every broadcast and before each of the above callback methods. You normally don’t need to implement this method because the default AppWidgetProvider implementation filters all App Widget broadcasts and calls the above methods as appropriate.

Note: In Android 1.5, there is a known issue in which the onDeleted() method will not be called when it should be. To work around this issue, you can implementonReceive() as described in this Group post to receive the onDeleted() callback.

The most important AppWidgetProvider callback is onUpdate() because it is called when each App Widget is added to a host (unless you use a configuration Activity). If your App Widget accepts any user interaction events, then you need to register the event handlers in this callback. If your App Widget doesn’t create temporary files or databases, or perform other work that requires clean-up, then onUpdate() may be the only callback method you need to define. For example, if you want an App Widget with a button that launches an Activity when clicked, you could use the following implementation of AppWidgetProvider:

public class ExampleAppWidgetProvider extends AppWidgetProvider {

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        final int N = appWidgetIds.length;

        // Perform this loop procedure for each App Widget that belongs to this provider
        for (int i=0; i<N; i++) {
            int appWidgetId = appWidgetIds[i];

            // Create an Intent to launch ExampleActivity
            Intent intent = new Intent(context, ExampleActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);

            // Get the layout for the App Widget and attach an on-click listener
            // to the button
            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
            views.setOnClickPendingIntent(R.id.button, pendingIntent);

            // Tell the AppWidgetManager to perform an update on the current app widget
            appWidgetManager.updateAppWidget(appWidgetId, views);
        }
    }
}

This AppWidgetProvider defines only the onUpdate() method for the purpose of defining a PendingIntent that launches an Activity and attaching it to the App Widget’s button with setOnClickPendingIntent(int, PendingIntent). Notice that it includes a loop that iterates through each entry in appWidgetIds, which is an array of IDs that identify each App Widget created by this provider. In this way, if the user creates more than one instance of the App Widget, then they are all updated simultaneously. However, only oneupdatePeriodMillis schedule will be managed for all instances of the App Widget. For example, if the update schedule is defined to be every two hours, and a second instance of the App Widget is added one hour after the first one, then they will both be updated on the period defined by the first one and the second update period will be ignored (they’ll both be updated every two hours, not every hour).

Note: Because AppWidgetProvider is an extension of BroadcastReceiver, your process is not guaranteed to keep running after the callback methods return (seeBroadcastReceiver for information about the broadcast lifecycle). If your App Widget setup process can take several seconds (perhaps while performing web requests) and you require that your process continues, consider starting a Service in the onUpdate() method. From within the Service, you can perform your own updates to the App Widget without worrying about the AppWidgetProvider closing down due to an Application Not Responding (ANR) error. See the Wiktionary sample’s AppWidgetProvider for an example of an App Widget running a Service.

Also see the ExampleAppWidgetProvider.java sample class.

Receiving App Widget broadcast Intents

AppWidgetProvider is just a convenience class. If you would like to receive the App Widget broadcasts directly, you can implement your own BroadcastReceiver or override theonReceive(Context, Intent) callback. The four Intents you need to care about are:

Creating an App Widget Configuration Activity

If you would like the user to configure settings when he or she adds a new App Widget, you can create an App Widget configuration Activity. This Activity will be automatically launched by the App Widget host and allows the user to configure available settings for the App Widget at create-time, such as the App Widget color, size, update period or other functionality settings.

The configuration Activity should be declared as a normal Activity in the Android manifest file. However, it will be launched by the App Widget host with theACTION_APPWIDGET_CONFIGURE action, so the Activity needs to accept this Intent. For example:

<activity android:name=".ExampleAppWidgetConfigure">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
    </intent-filter>
</activity>

Also, the Activity must be declared in the AppWidgetProviderInfo XML file, with the android:configure attribute (see Adding the AppWidgetProviderInfo Metadata above). For example, the configuration Activity can be declared like this:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    android:configure="com.example.android.ExampleAppWidgetConfigure"
    ... >
</appwidget-provider>

Notice that the Activity is declared with a fully-qualified namespace, because it will be referenced from outside your package scope.

That’s all you need to get started with a configuration Activity. Now all you need is the actual Activity. There are, however, two important things to remember when you implement the Activity:

  • The App Widget host calls the configuration Activity and the configuration Activity should always return a result. The result should include the App Widget ID passed by the Intent that launched the Activity (saved in the Intent extras as EXTRA_APPWIDGET_ID).
  • The onUpdate() method will not be called when the App Widget is created (the system will not send the ACTION_APPWIDGET_UPDATE broadcast when a configuration Activity is launched). It is the responsibility of the configuration Activity to request an update from the AppWidgetManager when the App Widget is first created. However,onUpdate() will be called for subsequent updates—it is only skipped the first time.

See the code snippets in the following section for an example of how to return a result from the configuration and update the App Widget.

Updating the App Widget from the configuration Activity

When an App Widget uses a configuration Activity, it is the responsibility of the Activity to update the App Widget when configuration is complete. You can do so by requesting an update directly from the AppWidgetManager.

Here’s a summary of the procedure to properly update the App Widget and close the configuration Activity:

  1. First, get the App Widget ID from the Intent that launched the Activity:
    Intent intent = getIntent();
    Bundle extras = intent.getExtras();
    if (extras != null) {
        mAppWidgetId = extras.getInt(
                AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
    }
  2. Perform your App Widget configuration.
  3. When the configuration is complete, get an instance of the AppWidgetManager by calling getInstance(Context):
    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
  4. Update the App Widget with a RemoteViews layout by calling updateAppWidget(int, RemoteViews):
    RemoteViews views = new RemoteViews(context.getPackageName(),
    R.layout.example_appwidget);
    appWidgetManager.updateAppWidget(mAppWidgetId, views);
  5. Finally, create the return Intent, set it with the Activity result, and finish the Activity:
  6. Intent resultValue = new Intent();
    resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
    setResult(RESULT_OK, resultValue);
    finish();

Tip: When your configuration Activity first opens, set the Activity result to RESULT_CANCELED. This way, if the user backs-out of the Activity before reaching the end, the App Widget host is notified that the configuration was cancelled and the App Widget will not be added.

See the ExampleAppWidgetConfigure.java sample class in ApiDemos for an example.

Setting a Preview Image

Android 3.0 introduces the previewImage field, which specifies a preview of what the app widget looks like. This preview is shown to the user from the widget picker. If this field is not supplied, the app widget’s icon is used for the preview.

This is how you specify this setting in XML:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
  ...
  android:previewImage="@drawable/preview">
</appwidget-provider>

To help create a preview image for your app widget (to specify in the previewImage field), the Android emulator includes an application called “Widget Preview.” To create a preview image, launch this application, select the app widget for your application and set it up how you’d like your preview image to appear, then save it and place it in your application’s drawable resources.

Using App Widgets with Collections

Android 3.0 introduces App Widgets with collections. These kinds of App Widgets use the RemoteViewsService to display collections that are backed by remote data, such as from a content provider. The data provided by the RemoteViewsService is presented in the App Widget using one of the following view types, which we’ll refer to as “collection views:”

ListView
A view that shows items in a vertically scrolling list. For an example, see the Gmail app widget.
GridView
A view that shows items in two-dimensional scrolling grid. For an example, see the Bookmarks app widget.
StackView
A stacked card view (kind of like a rolodex), where the user can flick the front card up/down to see the previous/next card, respectively. Examples include the YouTube and Books app widgets.
AdapterViewFlipper
An adapter-backed simple ViewAnimator that animates between two or more views. Only one child is shown at a time.

As stated above, these collection views display collections backed by remote data. This means that they use an Adapter to bind their user interface to their data. An Adapter binds individual items from a set of data into individual View objects. Because these collection views are backed by adapters, the Android framework must include extra architecture to support their use in app widgets. In the context of an app widget, the Adapter is replaced by a RemoteViewsFactory, which is simply a thin wrapper around the Adapter interface. When requested for a specific item in the collection, the RemoteViewsFactory creates and returns the item for the collection as a RemoteViews object. In order to include a collection view in your app widget, you must implement RemoteViewsService and RemoteViewsFactory.

RemoteViewsService is a service that allows a remote adapter to request RemoteViews objects. RemoteViewsFactory is an interface for an adapter between a collection view (such as ListViewGridView, and so on) and the underlying data for that view. From the StackView Widget sample, here is an example of the boilerplate code you use to implement this service and interface:

public class StackWidgetService extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
    }
}

class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {

//... include adapter-like methods here. See the StackView Widget sample.

}

Sample application

The code excerpts in this section are drawn from the StackView Widget sample:

StackView Widget

This sample consists of a stack of 10 views, which display the values "0!" through "9!" The sample app widget has these primary behaviors:

  • The user can vertically fling the top view in the app widget to display the next or previous view. This is a built-in StackView behavior.
  • Without any user interaction, the app widget automatically advances through its views in sequence, like a slide show. This is due to the settingandroid:autoAdvanceViewId="@id/stack_view" in the res/xml/stackwidgetinfo.xml file. This setting applies to the view ID, which in this case is the view ID of the stack view.
  • If the user touches the top view, the app widget displays the Toast message “Touched view n,” where n is the index (position) of the touched view. For more discussion of how this is implemented, see Adding behavior to individual items.

Implementing app widgets with collections

To implement an App Widget with collections, you follow the same basic steps you would use to implement any app widget. The following sections describe the additional steps you need to perform to implement an App Widget with collections.

Manifest for app widgets with collections

In addition to the requirements listed in Declaring an App Widget in the Manifest, to make it possible for App Widgets with collections to bind to your RemoteViewsService, you must declare the service in your manifest file with the permission BIND_REMOTEVIEWS. This prevents other applications from freely accessing your app widget’s data. For example, when creating an App Widget that uses RemoteViewsService to populate a collection view, the manifest entry may look like this:

<service android:name="MyWidgetService"
...
android:permission="android.permission.BIND_REMOTEVIEWS" />

The line android:name="MyWidgetService" refers to your subclass of RemoteViewsService.

Layout for app widgets with collections

The main requirement for your app widget layout XML file is that it include one of the collection views: ListViewGridViewStackView, or AdapterViewFlipper. Here is thewidget_layout.xml for the StackView Widget sample:

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <StackView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/stack_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:loopViews="true" />
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/empty_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:background="@drawable/widget_item_background"
        android:textColor="#ffffff"
        android:textStyle="bold"
        android:text="@string/empty_view_text"
        android:textSize="20sp" />
</FrameLayout>

Note that empty views must be siblings of the collection view for which the empty view represents empty state.

In addition to the layout file for your entire app widget, you must create another layout file that defines the layout for each item in the collection (for example, a layout for each book in a collection of books). For example, the StackView Widget sample only has one layout file, widget_item.xml, since all items use the same layout. But the WeatherListWidget samplehas two layout files: dark_widget_item.xml and light_widget_item.xml.

AppWidgetProvider class for app widgets with collections

As with a regular app widget, the bulk of your code in your AppWidgetProvider subclass typically goes in onUpdate(). The major difference in your implementation foronUpdate() when creating an app widget with collections is that you must call setRemoteAdapter(). This tells the collection view where to get its data. TheRemoteViewsService can then return your implementation of RemoteViewsFactory, and the widget can serve up the appropriate data. When you call this method, you must pass an intent that points to your implementation of RemoteViewsService and the App Widget ID that specifies the app widget to update.

For example, here’s how the StackView Widget sample implements the onUpdate() callback method to set the RemoteViewsService as the remote adapter for the app widget collection:

public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
    // update each of the app widgets with the remote adapter
    for (int i = 0; i < appWidgetIds.length; ++i) {

        // Set up the intent that starts the StackViewService, which will
        // provide the views for this collection.
        Intent intent = new Intent(context, StackWidgetService.class);
        // Add the app widget ID to the intent extras.
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
        intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
        // Instantiate the RemoteViews object for the App Widget layout.
        RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
        // Set up the RemoteViews object to use a RemoteViews adapter.
        // This adapter connects
        // to a RemoteViewsService  through the specified intent.
        // This is how you populate the data.
        rv.setRemoteAdapter(appWidgetIds[i], R.id.stack_view, intent);

        // The empty view is displayed when the collection has no items.
        // It should be in the same layout used to instantiate the RemoteViews
        // object above.
        rv.setEmptyView(R.id.stack_view, R.id.empty_view);

        //
        // Do additional processing specific to this app widget...
        //

        appWidgetManager.updateAppWidget(appWidgetIds[i], rv);  
    }
    super.onUpdate(context, appWidgetManager, appWidgetIds);
}

RemoteViewsService class

Persisting data

You can’t rely on a single instance of your service, or any data it contains, to persist. You should therefore not store any data in your RemoteViewsService(unless it is static). If you want your app widget’s data to persist, the best approach is to use aContentProvider whose data persists beyond the process lifecycle.

As described above, your RemoteViewsService subclass provides the RemoteViewsFactory used to populate the remote collection view.

Specifically, you need to perform these steps:

  1. Subclass RemoteViewsServiceRemoteViewsService is the service through which a remote adapter can requestRemoteViews.
  2. In your RemoteViewsService subclass, include a class that implements the RemoteViewsFactory interface.RemoteViewsFactory is an interface for an adapter between a remote collection view (such as ListView,GridView, and so on) and the underlying data for that view. Your implementation is responsible for making aRemoteViews object for each item in the data set. This interface is a thin wrapper around Adapter.

The primary contents of the RemoteViewsService implementation is its RemoteViewsFactory, described below.

RemoteViewsFactory interface

Your custom class that implements the RemoteViewsFactory interface provides the app widget with the data for the items in its collection. To do this, it combines your app widget item XML layout file with a source of data. This source of data could be anything from a database to a simple array. In the StackView Widget sample, the data source is an array ofWidgetItems. The RemoteViewsFactory functions as an adapter to glue the data to the remote collection view.

The two most important methods you need to implement for your RemoteViewsFactory subclass are onCreate() and getViewAt() .

The system calls onCreate() when creating your factory for the first time. This is where you set up any connections and/or cursors to your data source. For example, the StackView Widget sample uses onCreate() to initialize an array of WidgetItem objects. When your app widget is active, the system accesses these objects using their index position in the array and the text they contain is displayed

Here is an excerpt from the the StackView Widget sample’s RemoteViewsFactory implementation that shows portions of the onCreate() method:

class StackRemoteViewsFactory implements
RemoteViewsService.RemoteViewsFactory {
    private static final int mCount = 10;
    private List<WidgetItem> mWidgetItems = new ArrayList<WidgetItem>();
    private Context mContext;
    private int mAppWidgetId;

    public StackRemoteViewsFactory(Context context, Intent intent) {
        mContext = context;
        mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
    }

    public void onCreate() {
        // In onCreate() you setup any connections / cursors to your data source. Heavy lifting,
        // for example downloading or creating content etc, should be deferred to onDataSetChanged()
        // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
        for (int i = 0; i < mCount; i++) {
            mWidgetItems.add(new WidgetItem(i + "!"));
        }
        ...
    }
...

The RemoteViewsFactory method getViewAt() returns a RemoteViews object corresponding to the data at the specified position in the data set. Here is an excerpt from theStackView Widget sample’s RemoteViewsFactory implementation:

public RemoteViews getViewAt(int position) {

    // Construct a remote views item based on the app widget item XML file,
    // and set the text based on the position.
    RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_item);
    rv.setTextViewText(R.id.widget_item, mWidgetItems.get(position).text);

    ...
    // Return the remote views object.
    return rv;
}

Adding behavior to individual items

The above sections show you how to bind your data to your app widget collection. But what if you want to add dynamic behavior to the individual items in your collection view?

As described in Using the AppWidgetProvider Class, you normally use setOnClickPendingIntent() to set an object’s click behavior—such as to cause a button to launch anActivity. But this approach is not allowed for child views in an individual collection item (to clarify, you could use setOnClickPendingIntent() to set up a global button in the Gmail app widget that launches the app, for example, but not on the individual list items). Instead, to add click behavior to individual items in a collection, you usesetOnClickFillInIntent(). This entails setting up up a pending intent template for your collection view, and then setting a fill-in intent on each item in the collection via yourRemoteViewsFactory.

This section uses the StackView Widget sample to describe how to add behavior to individual items. In the StackView Widget sample, if the user touches the top view, the app widget displays the Toast message “Touched view n,” where n is the index (position) of the touched view. This is how it works:

  • The StackWidgetProvider (an AppWidgetProvider subclass) creates a pending intent that has a custom action called TOAST_ACTION.
  • When the user touches a view, the intent is fired and it broadcasts TOAST_ACTION.
  • This broadcast is intercepted by the StackWidgetProvider‘s onReceive() method, and the app widget displays the Toast message for the touched view. The data for the collection items is provided by the RemoteViewsFactory, via the RemoteViewsService.

Note: The StackView Widget sample uses a broadcast, but typically an app widget would simply launch an activity in a scenario like this one.

Setting up the pending intent template

The StackWidgetProvider (AppWidgetProvider subclass) sets up a pending intent. Individuals items of a collection cannot set up their own pending intents. Instead, the collection as a whole sets up a pending intent template, and the individual items set a fill-in intent to create unique behavior on an item-by-item basis.

This class also receives the broadcast that is sent when the user touches a view. It processes this event in its onReceive() method. If the intent’s action is TOAST_ACTION, the app widget displays a Toast message for the current view.

public class StackWidgetProvider extends AppWidgetProvider {
    public static final String TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION";
    public static final String EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM";

    ...

    // Called when the BroadcastReceiver receives an Intent broadcast.
    // Checks to see whether the intent's action is TOAST_ACTION. If it is, the app widget
    // displays a Toast message for the current item.
    @Override
    public void onReceive(Context context, Intent intent) {
        AppWidgetManager mgr = AppWidgetManager.getInstance(context);
        if (intent.getAction().equals(TOAST_ACTION)) {
            int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
            int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);
            Toast.makeText(context, "Touched view " + viewIndex, Toast.LENGTH_SHORT).show();
        }
        super.onReceive(context, intent);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // update each of the app widgets with the remote adapter
        for (int i = 0; i < appWidgetIds.length; ++i) {

            // Sets up the intent that points to the StackViewService that will
            // provide the views for this collection.
            Intent intent = new Intent(context, StackWidgetService.class);
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
            // When intents are compared, the extras are ignored, so we need to embed the extras
            // into the data so that the extras will not be ignored.
            intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
            RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
            rv.setRemoteAdapter(appWidgetIds[i], R.id.stack_view, intent);

            // The empty view is displayed when the collection has no items. It should be a sibling
            // of the collection view.
            rv.setEmptyView(R.id.stack_view, R.id.empty_view);

            // This section makes it possible for items to have individualized behavior.
            // It does this by setting up a pending intent template. Individuals items of a collection
            // cannot set up their own pending intents. Instead, the collection as a whole sets
            // up a pending intent template, and the individual items set a fillInIntent
            // to create unique behavior on an item-by-item basis.
            Intent toastIntent = new Intent(context, StackWidgetProvider.class);
            // Set the action for the intent.
            // When the user touches a particular view, it will have the effect of
            // broadcasting TOAST_ACTION.
            toastIntent.setAction(StackWidgetProvider.TOAST_ACTION);
            toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
            intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
            PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
            rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent);

            appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
        }
    super.onUpdate(context, appWidgetManager, appWidgetIds);
    }
}
Setting the fill-in Intent

Your RemoteViewsFactory must set a fill-in intent on each item in the collection. This makes it possible to distinguish the individual on-click action of a given item. The fill-in intent is then combined with the PendingIntent template in order to determine the final intent that will be executed when the item is clicked.

public class StackWidgetService extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
    }
}

class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
    private static final int mCount = 10;
    private List<WidgetItem> mWidgetItems = new ArrayList<WidgetItem>();
    private Context mContext;
    private int mAppWidgetId;

    public StackRemoteViewsFactory(Context context, Intent intent) {
        mContext = context;
        mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
    }

    // Initialize the data set.
        public void onCreate() {
            // In onCreate() you set up any connections / cursors to your data source. Heavy lifting,
            // for example downloading or creating content etc, should be deferred to onDataSetChanged()
            // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
            for (int i = 0; i < mCount; i++) {
                mWidgetItems.add(new WidgetItem(i + "!"));
            }
           ...
        }
        ...

        // Given the position (index) of a WidgetItem in the array, use the item's text value in
        // combination with the app widget item XML file to construct a RemoteViews object.
        public RemoteViews getViewAt(int position) {
            // position will always range from 0 to getCount() - 1.

            // Construct a RemoteViews item based on the app widget item XML file, and set the
            // text based on the position.
            RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_item);
            rv.setTextViewText(R.id.widget_item, mWidgetItems.get(position).text);

            // Next, set a fill-intent, which will be used to fill in the pending intent template
            // that is set on the collection view in StackWidgetProvider.
            Bundle extras = new Bundle();
            extras.putInt(StackWidgetProvider.EXTRA_ITEM, position);
            Intent fillInIntent = new Intent();
            fillInIntent.putExtras(extras);
            // Make it possible to distinguish the individual on-click
            // action of a given item
            rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);

            ...

            // Return the RemoteViews object.
            return rv;
        }
    ...
    }

Keeping Collection Data Fresh

The following figure illustrates the flow that occurs in an App Widget that uses collections when updates occur. It shows how the App Widget code interacts with theRemoteViewsFactory, and how you can trigger updates:

One feature of App Widgets that use collections is the ability to provide users with up-to-date content. For example, consider the Android 3.0 Gmail app widget, which provides users with a snapshot of their inbox. To make this possible, you need to be able to trigger your RemoteViewsFactory and collection view to fetch and display new data. You achieve this with theAppWidgetManager call notifyAppWidgetViewDataChanged(). This call results in a callback to your RemoteViewsFactory’s onDataSetChanged() method, which gives you the opportunity to fetch any new data. Note that you can perform processing-intensive operations synchronously within the onDataSetChanged() callback. You are guaranteed that this call will be completed before the metadata or view data is fetched from the RemoteViewsFactory. In addition, you can perform processing-intensive operations within thegetViewAt() method. If this call takes a long time, the loading view (specified by the RemoteViewsFactory’s getLoadingView() method) will be displayed in the corresponding position of the collection view until it returns

Content Providers: introduccion

[Fuente: http://developer.android.com/guide/topics/providers/content-provider-basics.html]

Un content provider maneja el acceso a un repositorio central de datos. El provider es parte de una aplicación Android, que lo normal es que tenga su propio UI para trabajar con los datos. Sin embargo, los content providers son entendidos para ser utilizados por otras aplicaciones, que acceden al provider utilizando un objeto provider client. Juntos, providers y provider clients ofrecen un interfaz consistente y standard para acceder a los datos suponiendo otra forma de comunicación inter-procesos y de acceso seguro a los datos.

Este tema describe lo siguiente:

  • Como funcionan los content providers.
  • El API que tu usas para recuperar datos desde un content provider
  • El API que utilizas para insertar, actualizar o borrar datos en el content provider
  • Otras caracteristicas del API que facilitan trabajar con providers.

Introducción

Un content provider presenta datos a las aplicaciones externas como una o más tablas que son similares a las tablas encontradas en las BBDD relacionales. Una fila representa una instancia de algún tipo de datos que el provider contiene, y cada columna de la fila representa una pieza individual de datos recogidos para una instancia.

Por ejemplo, uno de los providers built-in en la plataforma Android es el diccionario del usuario, que contiene palabras no estandars que el usuario quiere guardar. La siguiente tabla ilustra cómo los datos están en la tabla del provider:

Table 1: Ejemplo de tabla de diccionario del usuario.

word app id frequency locale _ID
mapreduce user1 100 en_US 1
precompiler user14 200 fr_FR 2
applet user2 225 fr_CA 3
const user1 255 pt_BR 4
int user5 100 en_UK 5

En esta tabla , cada fila representa una instancia de una palabra que podria no ser encontrada en un diccionario estandar. Cada columna representa algunos datos de esa palabra, tales como el locale en las cuales fue por primera vez añadida. Las cabeceras de columan son los nombres de columnas que son almacenados en el provider. Para referenciar el locale  de una fila , nos referiremos a su columna ‘locale’. Para este provider, la columna _ID sirve como columna de clave primaria que es gestionada automaticamente por el provider.

Nota: Un provider no requiere tener una clave primaria, y por tanto no es necesario utilizar la columna _ID com el el nombre de columna de la clave primaria si no está presente. Sin embargo, si quieres enganchar datos de un provider a una ListView, una de las columnas tienen que ser “_ID”. Para más info Displaying query results.

Accediendo a un provider

Una aplicación accede a los datos de un content provider con ele objeto cliente ContentResolver. Este objeto tiene métodos que invocan métodos con los mismos nombres que tiene el objeto provider, una instancia de una de la subclases concretas de ContentProvider. Los métodos de ContentResolver proporcionan las funciones del basico “CRUD” de almacenamiento de persistencia.

El objeto ContentResolver en el proceso de la aplicacion cliente y el objeto ContentProvider en la aplicación que tiene el provider automaticamente manejan comunicación inter-procesos. ContentProvider también actua como una capa de abstracción entre su repositorio de datos y la apariencia externa de los datos de sus tablas.

Nota: Para acceder a un provider, tu aplicación generalmente tiene un permiso especifico en su fichero manifest. Para mas info  Content Provider Permissions

Por ejemplo, para recuperar una lista de palabras y sus locales del provider del diccionario del usuario, haremos ContentResolver.query(). The query() method calls the ContentProvider.query() method defined by the User Dictionary Provider. The following lines of code show a ContentResolver.query() call:

// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
    mProjection,                        // The columns to return for each row
    mSelectionClause                    // Selection criteria
    mSelectionArgs,                     // Selection criteria
    mSortOrder);                        // The sort order for the returned rows

 

La siguiente tabla muestra como los argumentos del método query(Uri,projection,selection,selectionArgs,sortOrder) corresponden con una sentencia SQL:

Table 2: Query() compared to SQL query.

query() argument SELECT keyword/parameter Notes
Uri FROM table_name Uri maps to the table in the provider named table_name.
projection col,col,col,... projection is an array of columns that should be included for each row retrieved.
selection WHERE colvalue selection specifies the criteria for selecting rows.
selectionArgs (No exact equivalent. Selection arguments replace ? placeholders in the selection clause.)
sortOrder ORDER BY col,col,... sortOrder specifies the order in which rows appear in the returned Cursor.

Los content URIs

Un content URI es una URI que identifica los datos en un provider. Los Content URIs incluyen el nombre simbólico del provider completo (su authority) y el nombre que apunta a la tabla (el path). Cuando invocamos un método cliente para acceder a una tabla en un provider, el content URI para la tabla es uno de los argumentos que debemos proporcionar

En las línea de código anteriores , la constante CONTENT_URI contiene el content URI del diccionario de palabras del usuario. El objeto ContentResolver parsea la authority de la  URI, y lo utiliza para resolver el provider por comparación del authority con una tabla que contiene el sistema con los providers conocidos. Entonces ContentResolver puede despachar los argumentos  de la query al provider correcto.

El ContentResolver utiliza el path de la content URI para saber qué tabla acceder. Un provider generalmente tiene una path cada tabla que expone.

En las lineas de código anteriores , el URI completo para la tabla “words” es el siguiente:

content://user_dictionary/words

where the user_dictionary string is the provider’s authority, and words string is the table’s path. The string content:// (the scheme) is always present, and identifies this as a content URI.

Many providers allow you to access a single row in a table by appending an ID value to the end of the URI. For example, to retrieve a row whose _ID is 4 from user dictionary, you can use this content URI:

Uri singleUri = ContentUri.withAppendedId(UserDictionary.Words.CONTENT_URI,4);

You often use id values when you’ve retrieved a set of rows and then want to update or delete one of them.

Note: The Uri and Uri.Builder classes contain convenience methods for constructing well-formed Uri objects from strings. The ContentUris contains convenience methods for appending id values to a URI. The previous snippet uses withAppendedId() to append an id to the UserDictionary content URI.

Leyendo datos desde un Provider

This section describes how to retrieve data from a provider, using the User Dictionary Provider as an example.

For the sake of clarity, the code snippets in this section call ContentResolver.query() on the “UI thread””. In actual code, however, you should do queries asynchronously on a separate thread. One way to do this is to use theCursorLoader class, which is described in more detail in the Loaders guide. Also, the lines of code are snippets only; they don’t show a complete application.

To retrieve data from a provider, follow these basic steps:

  1. Request the read access permission for the provider.
  2. Define the code that sends a query to the provider.

Requesting read access permission

To retrieve data from a provider, your application needs “read access permission” for the provider. You can’t request this permission at run-time; instead, you have to specify that you need this permission in your manifest, using the <uses-permission> element and the exact permission name defined by the provider. When you specify this element in your manifest, you are in effect “requesting” this permission for your application. When users install your application, they implicitly grant this request.

To find the exact name of the read access permission for the provider you’re using, as well as the names for other access permissions used by the provider, look in the provider’s documentation.

The role of permissions in accessing providers is described in more detail in the section Content Provider Permissions.

The User Dictionary Provider defines the permission android.permission.READ_USER_DICTIONARY in its manifest file, so an application that wants to read from the provider must request this permission.

Constructing the query

The next step in retrieving data a provider is to construct a query. This first snippet defines some variables for accessing the User Dictionary Provider:

// A "projection" defines the columns that will be returned for each row
String[] mProjection =
{
    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
    UserDictionary.Words.WORD,   // Contract class constant for the word column name
    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
};

// Defines a string to contain the selection clause
String mSelectionClause = null;

// Initializes an array to contain selection arguments
String[] mSelectionArgs = {""};

The next snippet shows how to use ContentResolver.query(), using the User Dictionary Provider as an example. A provider client query is similar to an SQL query, and it contains a set of columns to return, a set of selection criteria, and a sort order.

The set of columns that the query should return is called a projection (the variable mProjection).

The expression that specifies the rows to retrieve is split into a selection clause and selection arguments. The selection clause is a combination of logical and Boolean expressions, column names, and values (the variable mSelection). If you specify the replaceable parameter ? instead of a value, the query method retrieves the value from the selection arguments array (the variable mSelectionArgs).

In the next snippet, if the user doesn’t enter a word, the selection clause is set to null, and the query returns all the words in the provider. If the user enters a word, the selection clause is set to UserDictionary.Words.Word + " = ?" and the first element of selection arguments array is set to the word the user enters.

/*
 * This defines a one-element String array to contain the selection argument.
 */
String[] mSelectionArgs = {""};

// Gets a word from the UI
mSearchString = mSearchWord.getText().toString();

// Remember to insert code here to check for invalid or malicious input.

// If the word is the empty string, gets everything
if (TextUtils.isEmpty(mSearchString)) {
    // Setting the selection clause to null will return all words
    mSelectionClause = null;
    mSelectionArgs[0] = "";

} else {
    // Constructs a selection clause that matches the word that the user entered.
    mSelectionClause = " = ?";

    // Moves the user's input string to the selection arguments.
    mSelectionArgs[0] = mSearchString;

}

// Does a query against the table and returns a Cursor object
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
    mProjection,                       // The columns to return for each row
    mSelectionClause                   // Either null, or the word the user entered
    mSelectionArgs,                    // Either empty, or the string the user entered
    mSortOrder);                       // The sort order for the returned rows

// Some providers return null if an error occurs, others throw an exception
if (null == mCursor) {
    /*
     * Insert code here to handle the error. Be sure not to use the cursor! You may want to
     * call android.util.Log.e() to log this error.
     *
     */
// If the Cursor is empty, the provider found no matches
} else if (mCursor.getCount() < 1) {

    /*
     * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
     * an error. You may want to offer the user the option to insert a new row, or re-type the
     * search term.
     */

} else {
    // Insert code here to do something with the results

}

This query is analogous to the SQL statement:

SELECT _ID, word, frequency, locale FROM words WHERE word = <userinput> ORDER BY word ASC;

In this SQL statement, the actual column names are used instead of contract class constants.

Protecting against malicious input

If the data managed by the content provider is in an SQL database, including external untrusted data into raw SQL statements can lead to SQL injection.

Consider this selection clause:

// Constructs a selection clause by concatenating the user's input to the column name
String mSelectionClause =  "var = " + mUserInput;

If you do this, you’re allowing the user to concatenate malicious SQL onto your SQL statement. For example, the user could enter “nothing; DROP TABLE *;” for mUserInput, which would result in the selection clause var = nothing; DROP TABLE *;. Since the selection clause is treated as an SQL statement, this might cause the provider to erase all of the tables in the underlying SQLite database (unless the provider is set up to catch SQL injection attempts).

To avoid this problem, use a selection clause that uses ? as a replaceable parameter and a separate array of selection arguments. When you do this, the user input is bound directly to the query rather than being interpreted as part of an SQL statement. Because it’s not treated as SQL, the user input can’t inject malicious SQL. Instead of using concatenation to include the user input, use this selection clause:

// Constructs a selection clause with a replaceable parameter
String mSelectionClause =  "var = ?";

Set up the array of selection arguments like this:

// Defines an array to contain the selection arguments
String[] selectionArgs = {""};

Put a value in the selection arguments array like this:

// Sets the selection argument to the user's input
selectionArgs[0] = mUserInput;

A selection clause that uses ? as a replaceable parameter and an array of selection arguments array are preferred way to specify a selection, even the provider isn’t based on an SQL database.

Displaying query results

The ContentResolver.query() client method always returns a Cursor containing the columns specified by the query’s projection for the rows that match the query’s selection criteria. A Cursor object provides random read access to the rows and columns it contains. Using Cursor methods, you can iterate over the rows in the results, determine the data type of each column, get the data out of a column, and examine other properties of the results. Some Cursorimplementations automatically update the object when the provider’s data changes, or trigger methods in an observer object when the Cursor changes, or both.

Note: A provider may restrict access to columns based on the nature of the object making the query. For example, the Contacts Provider restricts access for some columns to sync adapters, so it won’t return them to an activity or service.

If no rows match the selection criteria, the provider returns a Cursor object for which Cursor.getCount() is 0 (an empty cursor).

If an internal error occurs, the results of the query depend on the particular provider. It may choose to return null, or it may throw an Exception.

Since a Cursor is a “list” of rows, a good way to display the contents of a Cursor is to link it to a ListView via a SimpleCursorAdapter.

The following snippet continues the code from the previous snippet. It creates a SimpleCursorAdapter object containing the Cursor retrieved by the query, and sets this object to be the adapter for a ListView:

// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] mWordListColumns =
{
    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
};

// Defines a list of View IDs that will receive the Cursor columns for each row
int[] mWordListItems = { R.id.dictWord, R.id.locale};

// Creates a new SimpleCursorAdapter
mCursorAdapter = new SimpleCursorAdapter(
    getApplicationContext(),               // The application's Context object
    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
    mCursor,                               // The result from the query
    mWordListColumns,                      // A string array of column names in the cursor
    mWordListItems,                        // An integer array of view IDs in the row layout
    0);                                    // Flags (usually none are needed)

// Sets the adapter for the ListView
mWordList.setAdapter(mCursorAdapter);

Note: To back a ListView with a Cursor, the cursor must contain a column named _ID. Because of this, the query shown previously retrieves the _ID column for the “words” table, even though the ListView doesn’t display it. This restriction also explains why most providers have a _ID column for each of their tables.

Getting data from query results

Rather than simply displaying query results, you can use them for other tasks. For example, you can retrieve spellings from the user dictionary and then look them up in other providers. To do this, you iterate over the rows in the Cursor:

// Determine the column index of the column named "word"
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);

/*
 * Only executes if the cursor is valid. The User Dictionary Provider returns null if
 * an internal error occurs. Other providers may throw an Exception instead of returning null.
 */

if (mCursor != null) {
    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
     * exception.
     */
    while (mCursor.moveToNext()) {

        // Gets the value from the column.
        newWord = mCursor.getString(index);

        // Insert code here to process the retrieved word.

        ...

        // end of while loop
    }
} else {

    // Insert code here to report an error if the cursor is null or the provider threw an exception.
}

Cursor implementations contain several “get” methods for retrieving different types of data from the object. For example, the previous snippet uses getString(). They also have a getType() method that returns a value indicating the data type of the column.

Content Provider Permissions

A provider’s application can specify permissions that other applications must have in order to access the provider’s data. These permissions ensure that the user knows what data an application will try to access. Based on the provider’s requirements, other applications request the permissions they need in order to access the provider. End users see the requested permissions when they install the application.

If a provider’s application doesn’t specify any permissions, then other applications have no access to the provider’s data. However, components in the provider’s application always have full read and write access, regardless of the specified permissions.

As noted previously, the User Dictionary Provider requires the android.permission.READ_USER_DICTIONARY permission to retrieve data from it. The provider has the separate android.permission.WRITE_USER_DICTIONARYpermission for inserting, updating, or deleting data.

To get the permissions needed to access a provider, an application requests them with a <uses-permission> element in its manifest file. When the Android Package Manager installs the application, a user must approve all of the permissions the application requests. If the user approves all of them, Package Manager continues the installation; if the user doesn’t approve them, Package Manager aborts the installation.

The following <uses-permission> element requests read access to the User Dictionary Provider:

    <uses-permission android:name="android.permission.READ_USER_DICTIONARY">

The impact of permissions on provider access is explained in more detail in the Security and Permissions guide.

Inserting, Updating, and Deleting Data

In the same way that you retrieve data from a provider, you also use the interaction between a provider client and the provider’s ContentProvider to modify data. You call a method of ContentResolver with arguments that are passed to the corresponding method of ContentProvider. The provider and provider client automatically handle security and inter-process communication.

Inserting data

To insert data into a provider, you call the ContentResolver.insert() method. This method inserts a new row into the provider and returns a content URI for that row. This snippet shows how to insert a new word into the User Dictionary Provider:

// Defines a new Uri object that receives the result of the insertion
Uri mNewUri;

...

// Defines an object to contain the new values to insert
ContentValues mNewValues = new ContentValues();

/*
 * Sets the values of each column and inserts the word. The arguments to the "put"
 * method are "column name" and "value"
 */
mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
mNewValues.put(UserDictionary.Words.WORD, "insert");
mNewValues.put(UserDictionary.Words.FREQUENCY, "100");

mNewUri = getContentResolver().insert(
    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
    mNewValues                          // the values to insert
);

The data for the new row goes into a single ContentValues object, which is similar in form to a one-row cursor. The columns in this object don’t need to have the same data type, and if you don’t want to specify a value at all, you can set a column to null using ContentValues.putNull().

The snippet doesn’t add the _ID column, because this column is maintained automatically. The provider assigns a unique value of _ID to every row that is added. Providers usually use this value as the table’s primary key.

The content URI returned in newUri identifies the newly-added row, with the following format:

content://user_dictionary/words/<id_value>

The <id_value> is the contents of _ID for the new row. Most providers can detect this form of content URI automatically and then perform the requested operation on that particular row.

To get the value of _ID from the returned Uri, call ContentUris.parseId().

Updating data

To update a row, you use a ContentValues object with the updated values just as you do with an insertion, and selection criteria just as you do with a query. The client method you use is ContentResolver.update(). You only need to add values to the ContentValues object for columns you’re updating. If you want to clear the contents of a column, set the value to null.

The following snippet changes all the rows whose locale has the language “en” to a have a locale of null. The return value is the number of rows that were updated:

// Defines an object to contain the updated values
ContentValues mUpdateValues = new ContentValues();

// Defines selection criteria for the rows you want to update
String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
String[] mSelectionArgs = {"en_%"};

// Defines a variable to contain the number of updated rows
int mRowsUpdated = 0;

...

/*
 * Sets the updated value and updates the selected words.
 */
mUpdateValues.putNull(UserDictionary.Words.LOCALE);

mRowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mUpdateValues                       // the columns to update
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);

You should also sanitize user input when you call ContentResolver.update(). To learn more about this, read the section Protecting against malicious input.

Deleting data

Deleting rows is similar to retrieving row data: you specify selection criteria for the rows you want to delete and the client method returns the number of deleted rows. The following snippet deletes rows whose appid matches “user”. The method returns the number of deleted rows.

// Defines selection criteria for the rows you want to delete
String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] mSelectionArgs = {"user"};

// Defines a variable to contain the number of rows deleted
int mRowsDeleted = 0;

...

// Deletes the words that match the selection criteria
mRowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);

You should also sanitize user input when you call ContentResolver.delete(). To learn more about this, read the section Protecting against malicious input.

Provider Data Types

Content providers can offer many different data types. The User Dictionary Provider offers only text, but providers can also offer the following formats:

  • integer
  • long integer (long)
  • floating point
  • long floating point (double)

Another data type that providers often use is Binary Large OBject (BLOB) implemented as a 64KB byte array. You can see the available data types by looking at the Cursor class “get” methods.

The data type for each column in a provider is usually listed in its documentation. The data types for the User Dictionary Provider are listed in the reference documentation for its contract class UserDictionary.Words (contract classes are described in the section Contract Classes). You can also determine the data type by calling Cursor.getType().

Providers also maintain MIME data type information for each content URI they define. You can use the MIME type information to find out if your application can handle data that the provider offers, or to choose a type of handling based on the MIME type. You usually need the MIME type when you are working with a provider that contains complex data structures or files. For example, the ContactsContract.Data table in the Contacts Provider uses MIME types to label the type of contact data stored in each row. To get the MIME type corresponding to a content URI, call ContentResolver.getType().

The section MIME Type Reference describes the syntax of both standard and custom MIME types.

Alternative Forms of Provider Access

Three alternative forms of provider access are important in application development:

  • Batch access: You can create a batch of access calls with methods in the ContentProviderOperation class, and then apply them with ContentResolver.applyBatch().
  • Asynchronous queries: You should do queries in a separate thread. One way to do this is to use a CursorLoader object. The examples in the Loaders guide demonstrate how to do this.
  • Data access via intents: Although you can’t send an intent directly to a provider, you can send an intent to the provider’s application, which is usually the best-equipped to modify the provider’s data.

Batch access and modification via intents are described in the following sections.

Batch access

Batch access to a provider is useful for inserting a large number of rows, or for inserting rows in multiple tables in the same method call, or in general for performing a set of operations across process boundaries as a transaction (an atomic operation).

To access a provider in “batch mode”, you create an array of ContentProviderOperation objects and then dispatch them to a content provider with ContentResolver.applyBatch(). You pass the content provider’s authority to this method, rather than a particular content URI, which allows each ContentProviderOperation object in the array to work against a different table. A call to ContentResolver.applyBatch() returns an array of results.

The description of the ContactsContract.RawContacts contract class includes a code snippet that demonstrates batch insertion. The Contact Manager sample application contains an example of batch access in itsContactAdder.java source file.

Displaying data using a helper app

If your application does have access permissions, you still may want to use an intent to display data in another application. For example, the Calendar application accepts an ACTION_VIEW intent, which displays a particular date or event. This allows you to display calendar information without having to create your own UI. To learn more about this feature, see the Calendar Provider guide.

The application to which you send the intent doesn’t have to be the application associated with the provider. For example, you can retrieve a contact from the Contact Provider, then send anACTION_VIEW intent containing the content URI for the contact’s image to an image viewer.

Data access via intents

Intents can provide indirect access to a content provider. You allow the user to access data in a provider even if your application doesn’t have access permissions, either by getting a result intent back from an application that has permissions, or by activating an application that has permissions and letting the user do work in it.

Getting access with temporary permissions

You can access data in a content provider, even if you don’t have the proper access permissions, by sending an intent to an application that does have the permissions and receiving back a result intent containing “URI” permissions. These are permissions for a specific content URI that last until the activity that receives them is finished. The application that has permanent permissions grants temporary permissions by setting a flag in the result intent:

Note: These flags don’t give general read or write access to the provider whose authority is contained in the content URI. The access is only for the URI itself.

A provider defines URI permissions for content URIs in its manifest, using the android:grantUriPermission attribute of the <provider> element, as well as the <grant-uri-permission> child element of the <provider> element. The URI permissions mechanism is explained in more detail in the Security and Permissions guide, in the section “URI Permissions”.

For example, you can retrieve data for a contact in the Contacts Provider, even if you don’t have the READ_CONTACTS permission. You might want to do this in an application that sends e-greetings to a contact on his or her birthday. Instead of requesting READ_CONTACTS, which gives you access to all of the user’s contacts and all of their information, you prefer to let the user control which contacts are used by your application. To do this, you use the following process:

  1. Your application sends an intent containing the action ACTION_PICK and the “contacts” MIME type CONTENT_ITEM_TYPE, using the method startActivityForResult().
  2. Because this intent matches the intent filter for the People app’s “selection” activity, the activity will come to the foreground.
  3. In the selection activity, the user selects a contact to update. When this happens, the selection activity calls setResult(resultcode, intent) to set up a intent to give back to your application. The intent contains the content URI of the contact the user selected, and the “extras” flags FLAG_GRANT_READ_URI_PERMISSION. These flags grant URI permission to your app to read data for the contact pointed to by the content URI. The selection activity then calls finish() to return control to your application.
  4. Your activity returns to the foreground, and the system calls your activity’s onActivityResult() method. This method receives the result intent created by the selection activity in the People app.
  5. With the content URI from the result intent, you can read the contact’s data from the Contacts Provider, even though you didn’t request permanent read access permission to the provider in your manifest. You can then get the contact’s birthday information or his or her email address and then send the e-greeting.

Using another application

A simple way to allow the user to modify data to which you don’t have access permissions is to activate an application that has permissions and let the user do the work there.

For example, the Calendar application accepts an ACTION_INSERT intent, which allows you to activate the application’s insert UI. You can pass “extras” data in this intent, which the application uses to pre-populate the UI. Because recurring events have a complex syntax, the preferred way of inserting events into the Calendar Provider is to activate the Calendar app with an ACTION_INSERT and then let the user insert the event there.

Contract Classes

A contract class defines constants that help applications work with the content URIs, column names, intent actions, and other features of a content provider. Contract classes are not included automatically with a provider; the provider’s developer has to define them and then make them available to other developers. Many of the providers included with the Android platform have corresponding contract classes in the package android.provider.

For example, the User Dictionary Provider has a contract class UserDictionary containing content URI and column name constants. The content URI for the “words” table is defined in the constantUserDictionary.Words.CONTENT_URI. The UserDictionary.Words class also contains column name constants, which are used in the example snippets in this guide. For example, a query projection can be defined as:

String[] mProjection =
{
    UserDictionary.Words._ID,
    UserDictionary.Words.WORD,
    UserDictionary.Words.LOCALE
};

Another contract class is ContactsContract for the Contacts Provider. The reference documentation for this class includes example code snippets. One of its subclasses, ContactsContract.Intents.Insert, is a contract class that contains constants for intents and intent data.

MIME Type Reference

Content providers can return standard MIME media types, or custom MIME type strings, or both.

MIME types have the format

type/subtype

For example, the well-known MIME type text/html has the text type and the html subtype. If the provider returns this type for a URI, it means that a query using that URI will return text containing HTML tags.

Custom MIME type strings, also called “vendor-specific” MIME types, have more complex type and subtype values. The type value is always

vnd.android.cursor.dir

for multiple rows, or

vnd.android.cursor.item

for a single row.

The subtype is provider-specific. The Android built-in providers usually have a simple subtype. For example, the when the Contacts application creates a row for a telephone number, it sets the following MIME type in the row:

vnd.android.cursor.item/phone_v2

Notice that the subtype value is simply phone_v2.

Other provider developers may create their own pattern of subtypes based on the provider’s authority and table names. For example, consider a provider that contains train timetables. The provider’s authority is com.example.trains, and it contains the tables Line1, Line2, and Line3. In response to the content URI

content://com.example.trains/Line1

for table Line1, the provider returns the MIME type

vnd.android.cursor.dir/vnd.example.line1

In response to the content URI

content://com.example.trains/Line2/5

for row 5 in table Line2, the provider returns the MIME type

vnd.android.cursor.item/vnd.example.line2

Most content providers define contract class constants for the MIME types they use. The Contacts Provider contract class ContactsContract.RawContacts, for example, defines the constant CONTENT_ITEM_TYPE for the MIME type of a single raw contact row.

Content URIs for single rows are described in the section Content URIs.

Fundamentos de aplicaciones Android

[Fuente: http://developer.android.com/guide/topics/fundamentals.html]

Las aplicaciones Android están escritas en el lenguaje de programación Java. Las herramientas del Android SDK compilan el código. a la vez que otros ficheros de datos y de recursos- en un “package Android” , un fichero con extensión .apk. Todo el código se encuentra en un solo fichero .apk que se le considera una sola aplicación y es el fichero que los dispositivos Android utilizan para instalar la aplicación.

Una vez instalado sobre un dispositivo, cada aplicación Android vive en su propia sandbox de seguridad:

  • El sistema operativo Android es un sistema Linux multi-usuario en el cual cada aplicación es un usuario distinto.
  • Por defecto, el sistema asigna a cada aplicación un ID de usuario único (este ID es utilizado solo por el sistema y es deconocido por la aplicación). El sistema configura permisos para todos los ficheros de una aplicación de forma que que solo el usuario con el ID apropiado puede utilizar los recursos asociados a su aplicación.
  • Cada proceso tiene su propia máquina virtual (VM), asi el código de una aplicación se ejecuta aisladamente de otras aplicaciones.
  • Por defecto, todas las aplicaciones se ejecutan en su propio proceso Linux. Android comienza el proceso cuando cualquiera de los componentes de la aplicación necesita ser ejecutado, entonces apaga el proceso cuando ya no es necesitado o cuando el sistema debe recuperar memoria para otras aplicaciones.De esta forma , el sistema Android implementa el principio de el mínimo privilegio : cada aplicación , por defecto, tiene acceso solo a los componentes que requiere para hacer su trabajo y no más. Esto creo un entorno muy seguro en el cual una aplicación no puede acceder a partes del sistema para los cuales no se le ha dado permiso.

Sin embargo, hay formas para que una aplicación comparta datos con otras aplicaciones y que una aplicación pueda acceder a servicios del sistema:

  • Es posible organizar que dos aplicaciones compartan el mismo Linux user ID, en tal caso las dos pueden acceder a los ficheros de la otra. Para conservar los recursos del sistema, las aplicaciones con el mismo user ID pueden configurarse para ejecutar en el mismo proceso Linux y asi compartir la misma VM (las aplicaciones deben también estar asignadas con el mismo certificado).
  • Una aplicación puede requerir permisos para acceder a datos del dispositivo tales como los contactos del usuario, sus mensajes SMS, el alamacenamiento montable (SD card), cámara , bluetooth, y más. Todos los permisos de las aplicaciones deben ser aceptados por el usuario en tiempo de instalación.

Eso cubre lo que es el funcionamiento básico de cómo una aplicación Android existe dentro del sistema. El resto de este documento introduce los siguientes aspectos:

  • Los componentes del core framework que definen tu aplicación.
  • El fichero de manifest en el que se declaran los componentes y caracteristicas de dispositivos requeridas por tu aplicación.
  • Los recursos que van aparte del código de la aplicación y permiten que tu aplicación optimice su comportamiento para una gran variedad de configuraciones de dispositivos.

Componentes de las aplicaciones

Los componentes de la aplicación son los bloques de construcción esenciales de una aplicacion Android. Cada componente es un punto diferente a través del cual el sistema puede entrar en tu aplicación. No todos los componentes son de hecho puntos de entrada para el usuario y algunos dependen de otros, pero cada uno existe con su propia entidad e interpreta un role especifico- cada uno es un bloque de construcción único que ayuda a definir el comportamiento general de la aplicación.

Hay cuatro tipos diferentes de componentes de aplicación. Cada tipo sirve para un proposito distinto y tiene un ciclo de vida distinto que define como los componentes son creados y destruidos.Los tipos son los siguientes:

Activities

Un activity representa una sola pantalla con un interfaz de usuario. Por ejemplo, una aplicación de email podría tener una actividad que muestre una lista de nuevos emails, otra activity para redactar un email y otra activity para leer emails. aunque las activities trabajan juntas para formar una experiencia de usuario coherente en una aplicación de email, cada una es independiente de las otras. Por tanto, una aplicación diferente puede empezar cualquiera de estas activities (si la aplicación de email lo permite). Por ejemplo, una aplicación que utiliza la cámara puede iniciar la activity de la aplicación de email que se encarga de redactar un nuevo email, para que el usuario pueda compartir una imagen.
Un activity se implementa como una subclase de Activity (para más info consulta la Activities developer guide).

Services

Un service es un componente que ejecuta en segundo plano para realizar operaciones costosas o para realizar trabajos con procesos remotos. Un servicio no tiene interfaz de usuario. Por ejemplo, un servicio puede reproducir musica en segundo plano mientras el usuario está en otra aplicación, o puede recuperar datos de la red sin bloquear la interacción del usuario con el activity.Otro componente, como un activity, puede empezar el servicio y dejarlo ejecutar o engancharlo a él para interaccionar con él.
Un servicio es un subclase de Service y para más info consulta la Services developer guide.

Content providers

Un content provider gestiona un conjunto de datos compartidos entre aplicaciones. Pueden almacenar los datos en el sistema de ficheros, en una BBDD SQLite, en una web, o en cualquier tipo de almacenamiento persistente al que tu aplicacion pueda acceder. A traves del content provider, otras aplicaciones pueden consultar o incluso modificar los datos (si el content provider lo permite).
Por ejemplo, el sistema Android proporciona un content provider que gestiona la información de los contactos del usuario.Por tanto, cualquier aplicaci’on con los permiso apropiados puede consultar parte del content provider (como por ejemplo ContactsContract.data) para leer y escribir info sobre una persona en particular. Los content providers también son útiles para leer y escribir datos que son privados a tu aplicación pero que no van a ser compartidos.Por ejemplo, la aplicación Note Pad utiliza un content provider para almacenar notes.
Un content provider se implementa con la subclase ContentProvider y debe implementar las funciones que le dicta el API que habilitan a otras aplicaciones para realizar transacciones. Para más info, consulta Content Providers developer guide.

Broadcast receivers

Un broadcast receiver es un componente que responde a anuncios del sistema wide broadcasts (de amplia difusión). Muchos broadcasts los origina el sistema- por ejemplo , un broadcast que anuncia que la pantalla se ha apagado, que la bateria esta baja , o que se acabada de tirar una foto con la cámara.
Las aplicaciones pueden también iniciar broadcasts – por ejemplo, para permitir a otras aplicaciones conocer que ciertos datos han sido descargados al dispositivo y están ya disponibles para utilizar.
Aunque los broadcasts receivers no muestran un interfaz de usuario, pueden crear un barra de notificación de estado (create a status bar notification) que alerta al usuario cuando un evento broadcast ocurre. Más frecuente, un broadcast receiver es solo un gateway a otros componentes y se le entiende que tiene que hacer muy poco trabajo.Por ejemplo, pueden iniciar un servicio para realizar alguna tarea de la que depende el evento.
Un broadcast receiver se implementa como una subclase de BroadcastReceiver y cada broadcast es distribuido como un Intentobject. For more information, see the BroadcastReceiver class.

Un aspecto que es único del diseño del sistema Android es que cualquier aplicación puede arrancar un componente de otra aplicación. Por ejemplo, si quieres que el usuario capture una foto con el dispositivo cámara, probablemente hay otra aplicación que hace eso y que tu aplicación puede usar, asi no tienes que programar un activity que haga fotos. No necesitas incorporar ni linkarte al código desde la aplicación de la cámara; en su vez , puedes simplemente arrancar el activity de la aplicación de cámara que captura la foto. Cuando la haya hecho, la foto es devuelta a la aplicación de forma que puedas utilizarla. Para el usuario, parece como si la cámara es de hecho parte de tu aplicación.

Cuando el sistema arranca un componente, comienza el proceso para esa aplicación (si no esta ya ejecutándose) e instancia las clases necesitadas para el componente. Por ejemplo, si nuestra aplicación arranca el activity de la aplicación cámara que captura la foto, esa ativity se ejecuta dentro del proceso que pertenece a la aplicación cámara, no se ejecuta en el proceso de nuestra aplicación. Por lo tanto, a diferencia de la mayoria de las aplicaciones de otros sistemas, las aplicaciones Android no tiene un solo entry point (no hay main()).

Debido a que el sistema ejecuta  cada aplicación en un proceso separado con permisos especificos que restringen el aceso a otras aplicaciones, tu aplicación no puede directamente activar un componente desde otra aplicación. El sistema Android, sin embargo, sí puede. Asi , para activar un componente en otra aplicación, hay que distribuir un mensaje al sistema que especifique la intención (intent) de arrancar un componente en particular. El sistema entonces activa el componente para ti.

Activando componentes

Tres de los cuatro tipos de componentes-activities, services y broadcast receivers- se activan por medio de un mensaje asincrono que llamamos un intent.Los Intents enlazan componentes individuales entre ellos en tiempo de ejecución (se puede pensar en ellos como los mensajeres que solicitan una acción a otros componentes), independientemente de que el componente pertenezca a tu aplicación o a otra.

Un intent es creado con un objeto Intent, que define un mensaje para activar o un componente especifico  o un tipo especifico de componente- un intent puede ser o explicito o implícito , respectivamente.

Para activities y services , un intent define la acción a realizar (por ejemplo, para hacer un “view” o un “send” de algo) y puede especificar la URI de los datos con los que hay que actuar (entre otras cosas que el componente que arranca puede necesitar saber). Por ejemplo, un intent puede hacer una request para un activity para mostrar una imagen o para abrir una página web. En algunos casos, puedes arrancar un activity para recibir un resultado, en tal caso, el activity también retorna el result en un Intent (por ejemplo, puedes hacer in intent para permitir al coger un contacto y devolverlo a tu aplicacion- el return intent incluye un URI apuntando al contacto elegido).

Para los broadcasts receivers. el intent simplemente define el anuncio que se esta difundiendo (por ejemplo , un mensaje broadcast para indicar que la bateria del dispositivo esta baja incluye solo un string action que indica “battery is low”).

El otro tipo de componente , el content provider, no es activado por los intents. Sino que es activado cuando es targeted por una request desde un ContentResolver. El content resolver maneja todas las transacciones directas con el content provider de forma que el componente que esta realizando transacciones con el provider no necesita invocar métodos en el objeto ContentResolver. Esto deja una capa de abstracción entre el content provider y el componente que pide la información (por seguridad).

Hay métodos para activar cada tipo de componente:

Para más información sobre utilizar intents, mira el documento Intents and Intent Filters. Para más información sobre activar componentes específicos consultese los siguientes documentos:ActivitiesServicesBroadcastReceiver and Content Providers.

El fichero Manifest

Antes de que el sistema Android pueda arrancar un componente de aplicación, el sistema debe saber si el componente existe leyendo al archivo AndroidManifest.xml de la aplicación , es decir el archivo manifest de la aplicación. Tu aplicación debe declarar todos sus componentes en este fichero, y debe estar localizado en el raíz del directorio del proyecto de la aplicación.

El manifest hace varias cosas ademas de declarar los componentes de la aplicación, tal como:

  • Identificar cualquier permiso del usuario que la aplicación requiere , tal como el acceso a Internet o acceso de solo lectura a los contacto del usuario.
  • Declarar el API Level mínimo requerido por la aplicación, basándose en los métodos del API que la aplicación utiliza.
  • Declarar las características hardware y software utilizadas o requeridas por la aplicación, tal como una cámara, servicios bluetooth o una pantalla multitouch.
  • Las librerias API que necesita la aplicación para ser linkadas (aparte del Android framework APIs) , tales como Google Maps library.

Declarando componentes

La tarea primera del manifest es informar al sistema sobre los componentes de la aplicación.Por ejemplo, un fichero del manifest puede declarar un activity de la siguiente forma:

<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
    <application android:icon="@drawable/app_icon.png" ... >
        <activity android:name="com.example.project.ExampleActivity"
                  android:label="@string/example_label" ... >
        </activity>
        ...
    </application>
</manifest>

En el elemento <application> , el atributo android:icon apunta a recursos para un icono que identifique la aplicación.

En el elemento <activity> , el atributo android:name especifica el fully qualified class name de la subclase Activity y el  android:label especifica una string que hace de label visible al usuario de esa actividad.

Debes declarar todos los componentes de la aplicación de esta forma:

Activities, services, y content providers que incluyes en tu código pero no declaras en el manifest no son visibles al sistema y, consecuentemente, nunca pueden ejecutarse. Sin embargo, los broadcast receivers pueden ser o declarados en el manifest o creados dinámicamente en el código (como objetos BroadcastReceiver) y registrados en el sistema invocando registerReceiver().

Para más información de cómo estructurar el fichero manifest de tu aplicación. ver The AndroidManifest.xml File documentation.

Declarando capacidades de los componentes

Como se ha discutido arriba , en el apartado de Activando Components, podemos utilizar un Intent para arrancar activities, services y broadcast receivers. Puedes hacerlo asi por medio de nombrar el component target (utilizando el component class name) en el intent. Sin embargo , el potencial real de intents yace en el concepto de los actions de intent.Con los intent actions, tu simplemente describes el tipo de action que quieres realizar (y opcionalmente, los datos con los cuales te gustaría realizar el action) y dejas que el sistema encuentre un component en el dispositivo que puede realizar el action y arrancarlo.Si hay varios components que pueden realizar el action descrito por el intent, entonces el usuario selecciona cuál utilizar.

La forma en la que el sistema identifica los components que pueden responder al intent es por comparación de el intent recibido con los intent filters proporcionados por el fichero manifest de otras aplicaciones del dispositivo.

Cuando declaras un component en el manifest de tu aplicación, puedes opcionalmente incluir intent filters que declaran las capacidades del component de forma que pueda responder a intents de otras aplicaciones. Puedes declarar un intent filter para tu component añadiendo el elemento <intent-filter> como un hijo del elemento de declaracion del component.

Por ejemplo, una aplicación de email con un activity para redactar un nuevo email podría declarar in intent filter en su manifest entry para decir qie responde “send” intents (es decir los que piden enviar un email). Un activity de tu aplicación puede entonces crear un inent con la action “send” (ACTION_SEND), entonces el sistema busca correspondencia con la aplicación de email , busca su activity “send” y lanza dicho activity cuando lo invoquemos con el método startActivity().

Para más información sobre cómo crear intent filters , ver Intents and Intent Filters document.

Declarando requerimientos de aplicación

Hay una gran variedad de dispositivos que llevan Android y no todos proporcionan las mismas características y capacidades. Para prevenir tu aplicación de ser instalada en dispositivos que carecen de características necesitadas por tu aplicación, es importante que definas un perfil claro de los tipos de dispositivos que tu aplicación soporta declarando requerimientos tanto software como de dispositivo en el fichero manifest.La mayoría de estas declaraciones son solo informaciones y el sistema no los lee, pero servicios externos como el Android Market los leen para hacer un filtro para los usuarios que buscan aplicaciones desde su dispositivo.

Por ejemplo, si tu aplicación requiere un cámara y utiliza APIs introducidos por Android 2.1 (API Level 7), deberias declarar estos como requerimientos en el fichero manifest. De esa forma, los dispositivos que no tienen cámara y tienen una versión Android más baja de la 2.1 no pueden instalar tu aplicación desde el Android Market.

Sin embargo, puedes también declarar que tu aplicación utiliza la cámara, pero no es un requisito indispensable. En ese caso, tu aplicación debe realizar un check en tiempo de ejecución de si el dispositivo tiene una cámara y deshabilitar cualquier caracter´sitca que utiliza la cámara si esta no está disponible.

Aqui son algunas de las características importantes de dispositivos que debes considerar cuando diseñas y desarrollas tu aplicación:

Tamaño y densidad de la pantalla

Para categorizar los dispositivos por el tamaño de pantalla, Android define dos características de cada dispositivo:
– Tamaño de pantalla (las dimensiones fisicas de la pantalla)
– Densidad de pantalla (la densidad fisica de los pixels de la pantalla , o dpi – puntos por pulgada)
Para simplificar todos los tipos distintos de configuraciones de pantalla, el sistema Android los generaliza en grupos selectos que los hace facil de targetear:
– Los tamaños de pantalla son: small, normal, large, and extra large.
– Las densidades de pantalla son: low density, medium density, high density, and extra high density.
Por defecto, tu aplicacion es compatible con todos los tamaños de pantalla y densidades, debido a que el sistema Android hace los ajustes apropiados a los recursos de tu UI layout y de imagenes. Sin embargo, debes crear layouts especializados para ciertos tamaños de pantalla y proporcionar imagenes especializadas para ciertas densidades, utilziando recursos de layout alternativos, y declarando en el manifest exactamente qué tamaño de pantallas tu aplicación soporta con el elemento <supports-screens>.For more information, see the Supporting Multiple Screens document.
Configuración de entrada de datos
Many devices provide a different type of user input mechanism, such as a hardware keyboard, a trackball, or a five-way navigation pad. If your application requires a particular kind of input hardware, then you should declare it in your manifest with the <uses-configuration> element. However, it is rare that an application should require a certain input configuration.
Características de dispositivo
There are many hardware and software features that may or may not exist on a given Android-powered device, such as a camera, a light sensor, bluetooth, a certain version of OpenGL, or the fidelity of the touchscreen. You should never assume that a certain feature is available on all Android-powered devices (other than the availability of the standard Android library), so you should declare any features used by your application with the <uses-feature> element.
Versión de Plataforma
Different Android-powered devices often run different versions of the Android platform, such as Android 1.6 or Android 2.3. Each successive version often includes additional APIs not available in the previous version. In order to indicate which set of APIs are available, each platform version specifies an API Level (for example, Android 1.0 is API Level 1 and Android 2.3 is API Level 9). If you use any APIs that were added to the platform after version 1.0, you should declare the minimum API Level in which those APIs were introduced using the<uses-sdk> element.

It’s important that you declare all such requirements for your application, because, when you distribute your application on Android Market, Market uses these declarations to filter which applications are available on each device. As such, your application should be available only to devices that meet all your application requirements.

For more information about how Android Market filters applications based on these (and other) requirements, see the Market Filters document.

Recursos de aplicación

An Android application is composed of more than just code—it requires resources that are separate from the source code, such as images, audio files, and anything relating to the visual presentation of the application. For example, you should define animations, menus, styles, colors, and the layout of activity user interfaces with XML files. Using application resources makes it easy to update various characteristics of your application without modifying code and—by providing sets of alternative resources—enables you to optimize your application for a variety of device configurations (such as different languages and screen sizes).

For every resource that you include in your Android project, the SDK build tools define a unique integer ID, which you can use to reference the resource from your application code or from other resources defined in XML. For example, if your application contains an image file named logo.png (saved in the res/drawable/ directory), the SDK tools generate a resource ID named R.drawable.logo, which you can use to reference the image and insert it in your user interface.

One of the most important aspects of providing resources separate from your source code is the ability for you to provide alternative resources for different device configurations. For example, by defining UI strings in XML, you can translate the strings into other languages and save those strings in separate files. Then, based on a language qualifier that you append to the resource directory’s name (such as res/values-fr/ for French string values) and the user’s language setting, the Android system applies the appropriate language strings to your UI.

Android supports many different qualifiers for your alternative resources. The qualifier is a short string that you include in the name of your resource directories in order to define the device configuration for which those resources should be used. As another example, you should often create different layouts for your activities, depending on the device’s screen orientation and size. For example, when the device screen is in portrait orientation (tall), you might want a layout with buttons to be vertical, but when the screen is in landscape orientation (wide), the buttons should be aligned horizontally. To change the layout depending on the orientation, you can define two different layouts and apply the appropriate qualifier to each layout’s directory name. Then, the system automatically applies the appropriate layout depending on the current device orientation.

For more about the different kinds of resources you can include in your application and how to create alternative resources for various device configurations, see the Application Resources developer guide.

Qué es Android

[Fuente: http://developer.android.com/guide/basics/what-is-android.html]

Android es una pila de software para dispositivos moviles que incluye un sistema operativo , aplicaciones de middleware y aplicaciones de sistema. El Android SDK proporciona las herramientas y los APIs necesarios para empezar a desarrollar aplicaciones en la plataforma Android utilizando el lenguaje de programación Java.

Características que proporciona Android

  • Framework de aplicaciones que habilita la reutilización y el reemplazo de componentes.
  • Máquina virtual Dalvik optimizada para dispositivos móviles.
  • Navegador integrado basado en el motor open source WebKit
  • Gráficos optimizados soportados por una libreria de gráficos 2D personalizada; gráficos 3D basados en OpenGL ES 1.0 (aceleración hardware opcional)
  • SQLite para almacenamiento estructurado de datos
  • Soporte multimedia para formatos comunes de audio, video e imágenes (MPEG4, H.264, MP3, AAC, AMR, JPG, PNG, GIF)
  • Telefonía GSM (dependiente del hardware)
  • Bluetooth, EDGE, 3G y WiFi (dependiente del hardware)
  • Camara , GPS, brújula y acelerómetro (dependiente del hardware)
  • Entorno de desarrollo “Rich” lo cual incluye un emulator de dispositivos, herramientas de depuración , perfilado de memoria y rendimiente y un plugin para Eclipse IDE.

Arquitectura Android

El siguiente diagrama muestra los componentes principales del sistema operativo Android. Cada sección se describe más abajo:

Android System Architecture

Aplicaciones

Android viene con un conjunto de aplicaciones core que incluye un cliente de email , un programa de SMS , calendario, mapas, navegador, contactos y otros. Todas las aplicaciones se escriben utilizando el lenguaje de programación Java.

Framework de aplicaciones

Proporcionando una plataforma de desarrollo abierta, Android ofrece a los programadores la capacidad de hacer aplicaciones ricas e innovadoras. Los programadores son libres de sacarle partido al hardware del dispositivo, información de ubicación, servicios que se estén ejecutando en segundo plano, configurar alarmas, añadir notificaciones a la barra de estado y mucho, mucho más.

Los programadores tienen acceso completo al mismo API que utilizan las aplicaciones del sistema. La arquitectura de aplicaciones estaá diseñada para simplificar la reutilización de componentes; cualquier aplicación puede publicar sus capacidades y cualquier otra aplicación puede entonces hacer uso de estas capacidades (sujeto eso si a las restricciones de seguridad  forzadas por el framework). Este mismo mecanismo permite que los componentes sean reemplazados por el usuario.

Por debajo de todas las aplicaciones hay un conjunto de servicios y sistemas, incluyendo:

  • Un conjunto de Views completo y extensible que puede ser utilizado para construir una aplicación, incluyendo listas, grids, cajas de texto, botones e incluso un navegador web integrado.
  • Content Providers que habilitan a las aplicaciones para acceder a los datos de otras aplicaciones (como por ejemplo Contactos), o para compartir sus propios datos
  • Un Resource Manager, que proporciona acceso a recursos de no-codigo tales como cadenas multiidioma, grágicos y ficheros de layout.
  • Un Notification Manager que habilita a todas las aplicaciones para mostrar alertas personalizadas en la barra de estado
  • Un Activity Manager que gestiona el ciclo de vida de las aplicaciones y proporciona un backstack común de navegación.

Librerias

Androis incluye un conjunto de librerias C/C++ utilizadas por varios componentes del sistema Android. Estas capacidades son ofrecidas a las programadores a través del Android application framework. Algunos de las core librerias son las siguientes:

  • Libreria C de sistema – una implementación derivada del BSD de la libreria estandar de C (libc), tuneada para dispositivos embebidos basados en Linux.
  • Libreria multimedia – basadas en el PacketVideo’s OpenCORE; las librerias que soportan reproducción y grabación de muchos formatos populares de audio y video, tales como archivos de imagenes estáticos, incluyendo MPEG4, H.264, MP3, AAC, AMR, JPG, and PNG.
  • Surface Manager  – que gestiona el acceso al subsistema de la pantalla y a capas de gráficas 2D y 3D desde multiples aplicaciones.
  • LibWebCore – un motor de navegador web moderno que sirve tanto para el navegador dle Android como para el visor web embebido.
  • SGL – el motor de gráficos 2D subyacente
  • 3D librerias – una implementación basada en OpenGL ES 1.0 APIs; las librerias utilizando o aceleración 3D hardware (cuando está disponible) o el altamente optimizado rasterizador 3D software incluido.
  • FreeType – renderizado de fuentes bitmap y vectoriales.
  • SQLite – un motor de BBDD relacional potente y ligero disponible para todas las aplicaciones.

Android Runtime

Android incluye un conjunto de librerias core que proporcionan la mayoria de la funcionalidad disponible de las librerias core del lenguaje de programación Java.

Todas las aplicaciones Android se ejecutan en su propio proceso, con su propia instancia de la máquina virtual Dalvik.Dalvik ha sido escrito de forma que pueda ejecutar varias VM de forma eficiente. La Dalvik VM ejecuta ficheros en el formato ejecutable de Dalvik (.dex) que está optimizada para que consuma lo minimo de memoria posible. La VM esta basada en registros, y ejecuta clases copiladas por un compilador de Java que ha sido transformado al formato .dex por la herramienta “dx” (incluida).

La Dalvik VM esta montado sobre un kernel de Linux para la funcionalidad subyacente tal como threading y gestión de la memoria a bajo nivel.

Kernel de Linux

Android se monta encima de una versión 2.6 de Linux para los servicios core del sistema , gestión de memoria , gestión de procesos , pilas de red y modelo de drivers. El kernel también actua como una capa de abstracción entre el hardwar ey el resto de software de aplicaciones que están por encima.

 

 

Como instalar el Market en el emulador de Android

[Fuente: http://blog.varunkumar.me/2010/11/how-to-install-android-market-in-google.html]

Android Emulator es un móvil virtual que va contenido dentro del Android SDK. Se ejecuta en tu equipo y puede ser utilizado para hacer desarrollos de aplicaciones web sin tener que comprar un dispositivo físico. Casi todas las características de hardware y software de un dispositivo móvil típico son emuladas dentro del emulador.

Sin embargo, si quieres probar algunas apps desde el Android Market, no puedes hacerlo porque el emulador no incluye el Android Market. Aqui veremos una guia paso a paso para habilitar el market de Android en el emulador.

Pasos para habilitar el Android Market en el emulador

  1. Descarga e instala la última versión del Android SDK. Asegurate que el driver ‘ADB Interface’ está instalada en tu equipo.
  2. El siguiente paso es crear un Android Virtual Device con los detalles que se muestran a continuación:
  3. Después de crear el dispositivo, nos vamos a disco a la ruta ‘%userprofile%/.android/avd’. Hallaremos una carpeta llamada ‘Demodevice.avd‘. Copiemos el archivo imagen de sistema “system.img” desde ‘SDK_LOCATION/platforms/android-8/images’ a la carpeta ‘Demodevice.avd’. Iniciemos el emulador especificando el tamaño de partición. Por linea de comandos:
  4. emulator –avd DemoDevice –partition-size 100
  5. 100 Mb serán permitidos por el espacio de usuario. El emulador se abre y parece como el de abajo. Nota: Si el emulador de comando no es reconocido, añade SDK_LOCATION/tools to the PATH variable
  6. Desde el cmd (linea de comandos), ejecuta 
    adb pull /system/build.prop

    Esto copiará el fichero build.prop a tu directorio actual de trabajo. Abrelo con un editor de texto plano y cambia la propiedad ‘ro.config.nocheckin=yes’. Esto se necesita para arreglar que no se congelen las descargas del market.

  7. Now, we have to push back the modified build.prop. This is not as easy as pulling it out. First, you have to mount the /system partition in read-write mode. Check out this to know how to do that. After the partition is remounted, execute the adb push command to push back the build.prop 
    adb push build.prop /system/


  8. The next thing is to install the Market application. Download ‘Vending.apk’ from this thread. Google Services framework is a pre-requisite for Android market. To get ‘GoogleServicesFramework.apk’, download upload.zip from here and extract the zip to find the .apk file inside /system/app. Download ‘GoogleServcesFramework.apk’ from here. Push both these .apk to the device. ‘
    adb push Vending.apk /system/app/
    adb push GoogleServicesFramework.apk /system/app/

    .’. Also, delete the  SdkSetup.apk from the device.

    adb shell rm /system/app/SdkSetup.apk

    ’. Make sure the SdkSetup.apk is deleted otherwise the emulator will be reverted back to its original stage.

  9. Close the emulator. Delete the images userdata.img, userdata-qemu.img and cache.img from ‘%userprofile%/.android/avd/DemoDevice.avd’. Start the emulator again from Android SDK and AVD Manager. Android Market will be available on the emulator now. On opening the market, you will be prompted to connect with your Google account. Connect with your Google account and install your favorite apps on the emulator.
Note: Installation of apps from Android market seems to be very slow in the emulator. Market app crashed couple of times before I get the chance to install the apps. Also, I could not find the most popular Android game ‘Angry Birds’ in the market. Installing the same from its .apk didn’t work either. I am trying to tweak the emulator to make it work smoothly with Android Market. If you have any tips for the same, please post them as comments here.
Update: Uttam Hoode has tried this approach with Android 2.3 SDK and it worked fine.

Android aplicación completa parte VI: Personalizando presentación de la lista de resultados

En esta parte, vamos a crear una view personalizada para tener asi una mejor presentación visual de los datos.

La vista utilizada hasta ahora para presentar los resultados de la búsqueda es bastante rudimentaria y plana. Si recordamos , los datos fueron pasados a una ListActivity la cual, una vez renderizado, parecía a algo como esto:

Vamos ahora a añadirle un poco de vistosidad al UI del activity. El primer paso es reemplazar el ArrayAdapter que tenemos e implementar un custom adapter. Nuestro adapter extenderá la clase arrayAdapter y sobreescribirá el método getView para así proporcionar una custom list View.

Recuerda que la clase de modelo Movie contiene información variada en relación a la película, entre las que están las siguientes:

  • Calificación: Movie rating
  • Fecha de estreno: Release date
  • Certification
  • Idioma: Language
  • URL de la imagen pequeña: Thumbnail image URL

Vamos a crear un custom layout que incluirá los datos estos para cada película de la lista de resultados. Cada fila de la lista debe quedar a algo parecido a esto:

La nueva vista de los resultados

El fichero XML que describe el layout de cada fila lo llamamos “movie_data_row.xml” y contiene lo siguiente:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
							android:layout_width="fill_parent"
							android:layout_height="?android:attr/listPreferredItemHeight"
							android:padding="6dip">

	<ImageView 	android:id="@+id/movie_thumb_icon"
							android:layout_width="wrap_content"
							android:layout_height="fill_parent"
							android:layout_marginRight="6dip"/>

	<LinearLayout	android:orientation="vertical"
								android:layout_width="0dip"
								android:layout_weight="1"
								android:layout_height="fill_parent">

		<TextView
				android:id="@+id/name_text_view"
				android:layout_width="fill_parent"
				android:layout_height="0dip"
				android:layout_weight="1"
				android:singleLine="true"
				android:ellipsize="marquee"
				android:textStyle="bold"
		/>

		<TextView
				android:id="@+id/rating_text_view"
				android:layout_width="fill_parent"
				android:layout_height="0dip"
				android:layout_weight="1"
				android:singleLine="true"
				android:ellipsize="marquee"
		/>

		<TextView
				android:id="@+id/released_text_view"
				android:layout_width="fill_parent"
				android:layout_height="0dip"
				android:layout_weight="1"
				android:singleLine="true"
				android:ellipsize="marquee"
		/>

		<TextView
				android:id="@+id/certification_text_view"
				android:layout_width="fill_parent"
				android:layout_height="0dip"
				android:layout_weight="1"
				android:singleLine="true"
				android:ellipsize="marquee"
		/>

		<TextView
			android:id="@+id/language_text_view"
			android:layout_width="fill_parent"
			android:layout_height="0dip"
			android:layout_weight="1"
			android:singleLine="true"
			android:ellipsize="marquee"
		/>

		<TextView
			android:id="@+id/adult_text_view"
			android:layout_width="fill_parent"
			android:layout_height="0dip"
			android:layout_weight="1"
			android:singleLine="true"
			android:ellipsize="marquee"
		/>

	</LinearLayout>

</LinearLayout>

Utilizamos un LinearLayout para el layout base y dentro incluimos un ImageView (que tiene la imagen thumb) y otro LinearLayout anidado que es el contenedor para varios TextView. Cada elemento se le asigna un ID único así que puede ser referenciado desde nuestro adaptador.

Un nuevo ArrayAdapter personalizado

Observese que un ArrayAdapter no puede usar el método setContentView que es típicamente utilizado por una Activity para declarar el layout que será utilizado. La forma de recuperar un layout XML durante el tiempo de ejecución es utilizando el LayoutInflater service. Esta clase se utiliza para instanciar ficheros XML de layout en sus objetos View correspondientes. Más específicamente, el método inflate se utiliza para inflar una nueva jeraquia de views desde el recurso xml especificado. Después de que hemos tomado la referencia de la view subyacente, podemos usarlo de la forma normal y modificar sus widgets internos, es decir, proporcionar el texto para los TextViews y cargar la imagen en el ImageView. Aquí está el código:

package com.javacodegeeks.android.apps.moviesearchapp.ui;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.javacodegeeks.android.apps.moviesearchapp.R;
import com.javacodegeeks.android.apps.moviesearchapp.io.FlushedInputStream;
import com.javacodegeeks.android.apps.moviesearchapp.model.Movie;
import com.javacodegeeks.android.apps.moviesearchapp.services.HttpRetriever;

public class MoviesAdapter extends ArrayAdapter<Movie> {
	private HttpRetriever httpRetriever = new HttpRetriever();
	private ArrayList<Movie> movieDataItems;
	private Activity context;

	public MoviesAdapter(Activity context, int textViewResourceId, ArrayList<Movie> movieDataItems) {
		super(context, textViewResourceId, movieDataItems);
		this.context = context;
		this.movieDataItems = movieDataItems;
	}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
	View view = convertView;
	if (view == null) {
		LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		view = vi.inflate(R.layout.movie_data_row, null);
	}

	Movie movie = movieDataItems.get(position);
	if (movie != null) {
		// name
		TextView nameTextView = (TextView) view.findViewById(R.id.name_text_view);
		nameTextView.setText(movie.name);
		// rating
		TextView ratingTextView = (TextView) view.findViewById(R.id.rating_text_view);
		ratingTextView.setText("Rating: " + movie.rating);
		// released
		TextView releasedTextView = (TextView) view.findViewById(R.id.released_text_view);
		releasedTextView.setText("Release Date: " + movie.released);
		// certification
		TextView certificationTextView = (TextView) view.findViewById(R.id.certification_text_view);
		certificationTextView.setText("Certification: " + movie.certification);
		// language
		TextView languageTextView = (TextView) view.findViewById(R.id.language_text_view);
		languageTextView.setText("Language: " + movie.language);
		// thumb image
		ImageView imageView = (ImageView) view.findViewById(R.id.movie_thumb_icon);
		String url = movie.retrieveThumbnail();
		if (url!=null) {
			Bitmap bitmap = fetchBitmapFromCache(url);
			if (bitmap==null) {
			new BitmapDownloaderTask(imageView).execute(url);
			} else {
			imageView.setImageBitmap(bitmap);
			}
		}	else {
			imageView.setImageBitmap(null);
		}
	}
	return view;
}

private LinkedHashMap<String, Bitmap> bitmapCache = new LinkedHashMap<String, Bitmap>();
private void addBitmapToCache(String url, Bitmap bitmap) {
	if (bitmap != null) {
		synchronized (bitmapCache) {
			bitmapCache.put(url, bitmap);
		}
	}
}

private Bitmap fetchBitmapFromCache(String url) {
	synchronized (bitmapCache) {
		final Bitmap bitmap = bitmapCache.get(url);
		if (bitmap != null) {
			// Bitmap found in cache
			// Move element to first position, so that it is removed last
			bitmapCache.remove(url);
			bitmapCache.put(url, bitmap);
			return bitmap;
		}
	}
	return null;
}

private class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
		private String url;
		private final WeakReference<ImageView> imageViewReference;

		public BitmapDownloaderTask(ImageView imageView) {
			imageViewReference = new WeakReference<ImageView>(imageView);
		}

		@Override
		protected Bitmap doInBackground(String... params) {
			url = params[0];
			InputStream is = httpRetriever.retrieveStream(url);
			if (is==null) {
				return null;
			}
			return BitmapFactory.decodeStream(new FlushedInputStream(is));
		}

		@Override
		protected void onPostExecute(Bitmap bitmap) {
			if (isCancelled()) {
				bitmap = null;
			}
			addBitmapToCache(url, bitmap);
			if (imageViewReference != null) {
				ImageView imageView = imageViewReference.get();
				if (imageView != null) {
					imageView.setImageBitmap(bitmap);
				}
			}
		}
	}
}

Dentro nuestro método getView, primero “inflamos” o configuramos el fichero XML layout y recuperamos la referencia de la View descrita. Entonces, tomamos la referencia de cada una de las views widgets utilizando el método findViewById. Para cada TextView proporcionamos el texto relevante, mientras que para cada ImageView proporcionamos un Bitmap que contiene la imagen Thumbnail.

Se utiliza un mecanismo básico de cacheado en este punto para eliminar la re-descarga de la misma imagen una y otra vez. No olvides que el método getView va a ser llamado múltiples veces cuando el usuario esté usando el interface, así que no queremos realizar peticiones HTTP para la misma imagen. Por esta razón, nos creamos un map conteniendo asociaciones URL-bitmap.Si la imagen no es encontrada en el cache, un tarea background es lanzada para recuperar la imagen (y la almacena en la cache para las próximas llamadas). La tarea background se llama “BitmapDownloaderTask” y extiende la clase AsyncTask.

También nótese que dentro de cada tarea, la instancia ImageView se referencia a través de un WeakReference.Esto se hace por razones de rendimiento y más específicamente para permitir al Garbage collector de la máquina virtual que limpie cualquier ImageView que pueda pertenecer a una activity ya acabada. En otras palabras, no queremos un activity que tenga referencias fuertes de sus ImageViews asi que estas puedan ser fácilmente limpiadas. Consultese

http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html

para más información.

Modificamos el ListActivity

Además del list activity, hay algunos cambios que deben ser hechos, vayamos con ellos. El cambio más importante es que el ArrayAdapter original es reemplazado por el que hemos creado personalizado. Rellenamos el adapter con los contenidos de los objetos resultados de búsqueda y entonces invocamos el método notifyDataSetChanged para notificar a la View enganchada que los datos subyacentes han sido cambiados y debería refrescarse. Este es el código de la nueva implementación:

package com.javacodegeeks.android.apps.moviesearchapp;

import java.util.ArrayList;
import android.app.ListActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import android.widget.Toast;
import com.javacodegeeks.android.apps.moviesearchapp.model.Movie;
import com.javacodegeeks.android.apps.moviesearchapp.ui.MoviesAdapter;

public class MoviesListActivity extends ListActivity {
	private static final String IMDB_BASE_URL = "http://m.imdb.com/title/";
	private ArrayList<Movie> moviesList = new ArrayList<Movie>();
	private MoviesAdapter moviesAdapter;

	@SuppressWarnings("unchecked")
	@Override
	public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.movies_layout);
	moviesAdapter = new MoviesAdapter(this, R.layout.movie_data_row, moviesList);
	moviesList = (ArrayList<Movie>) getIntent().getSerializableExtra("movies");
	setListAdapter(moviesAdapter);
	if (moviesList!=null && !moviesList.isEmpty()) {
	moviesAdapter.notifyDataSetChanged();
	moviesAdapter.clear();
	for (int i = 0; i < moviesList.size(); i++) {
	moviesAdapter.add(moviesList.get(i));
	}
	}
	moviesAdapter.notifyDataSetChanged();
	}

	@Override
	protected void onListItemClick(ListView l, View v, int position, long id) {
		super.onListItemClick(l, v, position, id);
		Movie movie = moviesAdapter.getItem(position);
		String imdbId = movie.imdbId;
		if (imdbId==null || imdbId.length()==0) {
		longToast(getString(R.string.no_imdb_id_found));
		return;
		}
		String imdbUrl = IMDB_BASE_URL + movie.imdbId;
		Intent imdbIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(imdbUrl));
		startActivity(imdbIntent);
	}

	public void longToast(CharSequence message) {
		Toast.makeText(this, message, Toast.LENGTH_LONG).show();
	}

}

Probemos ahora la aplicación. Cuando se inicia el “MoviesListActivity”, primero verás las películas del resultado y su correspondiente información. Lentamente , las imágenes de los thumbs irán apareciendo según vayan descargándose. No olvides que estas operaciones tienen lugar en el background. Date cuenta, que algunas movies, especialmente las antiguas no tienen thumbnail.

That’s it! You can download here the Eclipse project created so far.

Enviando formularios con jQuery Mobile

Haciendo submit con formularios Ajax

En jQuery Mobile , los submit de formularios son automáticamente manejados utilizando Ajax cuando sea posible, creando una transición suave entre el formulario y la página de resultado. Para asegurar que tus submits de formulario son enganchados bien, asegurate de especificar las propiedades action y method dentro de la tag form. Cuando no sean especificadas, el método por defecto lo hará con get, y el action será por defecto un path relativo a la página actual (el que sale de $.mobile.path.get())

Los formularios también aceptan atributos para transiciones por ejemplo de los anchors (tales como data-transition=’pop’ y el data-direction=’reverse’). Para hacer submit de un formulario sin Ajax, se puede deshabilitar el Ajax form handling globalmente, o a través del atributo data-ajax=”false”. El  atributo target (como por ejemplo target=”_blank”) tambien es tenido en cuenta en los formularios, y por defecto ira al target que maneje el navegador por defecto. Observese que , a diferencia de los anchors , el atributo rel no es permitido en los formularios.

Ejemplo por defecto de un formulario Ajax

Aqui demostraremos como funciona el ajax handling de submits de formularios. El form siguiente se configura para enviar una peticion get a un forms-sample-response.php. En el submit, jQuery Mobile se asegura que la url especificada es capaz de ser recuperada via Ajax , y la gestiona apropiadamente. Manten en mente que como las peticiones submit de HTTP normales, jQuery Mobile te permite bajarte  paginas de resultado para ser añadidas a la hash de Urls cuando la respuesta es devuelta satisfactoriamente. También permite hacer submits normales de formularios, hacer request tipo post que no contengan query parametros en el hash, asi que ellos no sean añadidos al bookmark de la url hash.

			<form action="forms-sample-response.php" method="get" class="ui-body ui-body-a ui-corner-all">
				<fieldset>
					<div data-role="fieldcontain">
						<label for="shipping" class="select">Shipping method:</label>
						<select name="shipping" id="shipping">
							<option value="Standard shipping">Standard: 7 day</option>
							<option value="Rush shipping">Rush: 3 days</option>
							<option value="Express shipping">Express: next day</option>
							<option value="Overnight shipping">Overnight</option>
						</select>
					</div>
					<button type="submit" data-theme="b" name="submit" value="submit-value">Submit</button>
				</fieldset>
			</form>
Ejemplos de formularios No-Ajax

Para prevenir que los submit de formularios sean automaticamente manejadas via Ajax, tan solo debemos añadir el data-ajax=”false” en el elemento form. También se puede hacer con la opción global ajaxEnabled.

El formulario siguiente es identico al de arriba excepto porque hemos añadido el atributo data-ajax=”false”. Cuando el botón de submit es presionado, el resultado es el refresco de la pagina completa.

			<form action="forms-sample-response.php" method="get" data-ajax="false">
				<fieldset>
					<div data-role="fieldcontain">
						<label for="shipping">Shipping method:</label>
						<select name="shipping" id="shipping">
							<option value="Standard shipping">Standard: 7 day</option>
							<option value="Rush shipping">Rush: 3 days</option>
							<option value="Express shipping">Express: next day</option>
							<option value="Overnight shipping">Overnight</option>
						</select>
					</div>
					<button type="submit" data-theme="b" name="submit" value="submit-value">Submit</button>
				</fieldset>
			</form>

Formularios auto submitting

Como si fueran HTML ordinario, puede también hacer submit de formularios a la misma URL que estas actualmente visualizando configurando para ello el atributo action del form a la URL, Esta página lo demuestra:

		<form action="forms-sample-selfsubmit.php" method="post">

			<fieldset data-role="controlgroup" data-type="horizontal" data-role="fieldcontain">
			    <legend>Testing</legend>
			    <div>
			    				        <input type="radio" name="gender" value="m" id="gender-1"  /><label for="gender-1">Male</label>
			        <input type="radio" name="gender" value="f" id="gender-2"  /><label for="gender-2">Female</label>
			    </div>
			</fieldset>

			<button type="submit">Submit</submit>
	</form>

Cuando se hace una request post haciendo submit a una pagina que ya está en el DOM, la URL de respuesta será identica a la de la pagina existente, ya que los metodos post no deben llevar parametros query string en la URL. En esta situación, jQuery Mobile reemplazará la página desde donde sea hace el submit por la pagina resultado cogiendo el body de la respuesta y encasquetandosela al page que ya hay en el DOM.

Android Aplicación completa Parte V: Lanzando nuevas activities con intents

En esta parte, veremos como lanzar una nueva Activity y cómo transferir datos de una Activity a otra.

Google ha publicado Guias de diseño de Task y Activities que describen los principios más importantes del framework de una aplicación Android, desde el alto nivel, perspectiva centrada en el usuario lo cual es útil para la interacción y los diseñadores de aplicaciones , así como para los programadores:

http://developer.android.com/guide/practices/ui_guidelines/activity_task_design.html

Deberíamos echar un vistazo a estas guias de diseño, pero por ahora solo comentaremos unas cuantas cosas sobre las Activities:

  • Las Activities son los bloques de construcción principales de las aplicaciones Android
  • Mientras el usuario se mueve por el interfaz, lo que hacen es iniciar Activities una tras otra
  • Cada activity tiene una ciclo de vida que es independiente de otras activities en su misma aplicación o tarea.
  • Android guarda un histórico de navegación lineal de activities que el usuario ha visitado (la pila de Activities)
  • Datos pueden ser intercambiados entre Activities utilizando la clase Intent.

Hasta ahora en esta serie de tutoriales, hemos realizado la búsqueda de películas dentro de la main activity y solo notificamos a los usuarios sobre los resultados encontrados utilizando Toasts. Ahora vamos a ir un paso más allá y vamos a utilizar una nueva Activity en la cual los resultados serán presentados como una lista. Por esta razón, la nueva Activity extenderá la clase ListActivity de Android, que es una clase especial de Activity que muestra una lista de campos enlazándose a un data source como puede ser un array o Cursor de un resultset.

Esta nueva activity será iniciada desde la activity principal por medio de un Intent. Utilizaremos el constructor Intent que se le pasa un Context y una clase target. En la documentación de referencia, dice que este constructor
proporciona una forma conveniente de crear un intent que se usará para ejecutar un nombre de clase hard-coded , en vez de que el sistema tenga que encontrar la clase apropiado por ti. Como sabemos cuál Activity queremos que maneje el Intent, este constructor nos sirve.

Para transferir datos entre dos activities, utilizaremos uno de los métodos sobrecargados de putExtra que ofrece la clase Intent. Los datos transferibles tienen que ser Serializable y como el Arraylist lo es por defecto, también tenemos que marcar nuestras clases de modelo (Movie/Person/Image) con el interface Serailizable. Finalmente, utilizaremos el método startActivity para lanzar la nueva activity.

Así, dentro de la clase “PerformMovieSearchTask” (que está definida como una inner class dentro de la activity principal) , utilizaremos el siguiente código para iniciar la nueva activity que manejará los resultados de la búsqueda:

...
Intent intent = new Intent(MovieSearchAppActivity.this, MoviesListActivity.class);
intent.putExtra("movies", result);
startActivity(intent);
...

No olvidemos marcar las clases de modelo como serializable:

...
public class Movie implements Serializable
...
public class Person implements Serializable
...
public class Image implements Serializable
...

1.- El nuevo activity

Para que el sistema encuentre la nueva activity, tenemos que declararla en nuestro fichero “AndroidManifest.xml”. Esto lo hacemos añadiendo la siguiente línea dentro del elemento “application”:

...
<activity android:name=".MoviesListActivity" />
...

La nueva ListActivity recuperará los datos del método onCreate. Para hacer esto, primero tomará una referencia de el intent que lanzó la actividad utilizando el método getIntent que retorna el intent original. Desde ahí , recuperamos los datos extendidos a través del método getSerializableExtra, donde utilizamos el nombre del ítem que fue previamente añadido con el método putExtra. Debido a que el método retorna un objeto Serializable, realizamos el casting necesario.

Para presentar los resultados de la lista necesitamos utilizar un ArrayAdapter, es decir un ListAdapter que maneja un ListView soportado por un array de objetos arbitrarios. El ArrayAdapter será inicializado con la lista de películas que viene del mani activity. Entonces utilizaremos el método setListAdapter para asociar nuestra listActivity con el ArrayAdapter.

También queremos que cada elemento de la lista sea clicable para que al pincharlos se abra el navegador de internet con el enlace correspondiente de la página IMDB para esa peli. Asi , sobreescribimos el método onListItemClick, el cual se llama cuando un ítem de la lista es seleccionado.Dentro de ese método, recuperamos el objeto Movie que es asociado con la lista de ítem utilizando el ArrayAdapter, encontramos el ID de IMDB y entonces construimos la URL que apunta al IMDB, que es al como esto:

Base URL: http://m.imdb.com/title/

Y detrás añadiremos el ID de la película correspondiente. Entonces, crearemos un nuevo Intent con el action set puesto a ACTION_VIEW.Este es el Activity Action que muestra datos al usuario. En caso de un URL, el navegador por defecto será iniciado y redirigido a la página target.La nueva activity que maneja el intent es iniciada con el método startActivity normal.

Veamos el código de la clase “MoviesListActivity”:

package com.javacodegeeks.android.apps.moviesearchapp;

import java.util.ArrayList;
import android.app.ListActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import com.javacodegeeks.android.apps.moviesearchapp.model.Movie;

public class MoviesListActivity extends ListActivity {
	private static final String IMDB_BASE_URL = "http://m.imdb.com/title/";
	private ArrayList<Movie> moviesList;
	private ArrayAdapter<Movie> moviesAdapter;

	@SuppressWarnings("unchecked")
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.movies_layout);
		moviesList = (ArrayList<Movie>) getIntent().getSerializableExtra("movies");
		moviesAdapter = new ArrayAdapter<Movie>(this, android.R.layout.simple_list_item_1, moviesList);
		setListAdapter(moviesAdapter);
	}

	@Override
	protected void onListItemClick(ListView l, View v, int position, long id) {
		super.onListItemClick(l, v, position, id);
		Movie movie = moviesAdapter.getItem(position);
		String imdbId = movie.imdbId;
		if (imdbId==null || imdbId.length()==0) {
			longToast(getString(R.string.no_imdb_id_found));
			return;
		}
		String imdbUrl = IMDB_BASE_URL + movie.imdbId;
		Intent imdbIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(imdbUrl));
		startActivity(imdbIntent);
	}

	public void longToast(CharSequence message) {
		Toast.makeText(this, message, Toast.LENGTH_LONG).show();
	}

}

Observa que el ArrayAdapter utiliza el método toString para cada uno de los objetos encapsulados, así sobreescribiremos el método y proporcionamos una representación con más significado (o más presentable):

@Override
public String toString() {
	StringBuilder builder = new StringBuilder();
	builder.append("Movie [name=");
	builder.append(name);
	builder.append("]");
	return builder.toString();
}

2.-La vista del nuevo activity

El fichero layout que describe la vista para la nueva actividad necesita tener una declaración ListView con el id “@+id/android:list”. Además declaramos un TextView con id “@+id/android:empty” que aparecerá en el caso que la lista no incluya resultados. Estas vistas están incluidas dentro de un LinearLayout, que es un layout que coloca sus elementos hijos en una sola columna o en una sola fila. El fichero XML lo ponemos en la carpeta “res/layout”, o llamamos “movies_layout.xml” y tiene el siguiente contenido:

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

	<ListView
		android:id="@+id/android:list"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"
	/>

	<TextView
		android:id="@+id/android:empty"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"
		android:text="@string/no_movies_found"
	/>

</LinearLayout>

Ahora, ejecutamos el emulador , realizamos una búsqueda y esperamos los resultados.

Una vez recuperado, la clase “MoviesListActivity” se invoca y los resultados son mostrados en una lista (invocando el método toString como dijimos).

Puedes hacer scroll hacia abajo (esto lo maneja Android) y hacemos click en la película que deseas para encontrar más detalles. Esto disparará un evento que abrirá el navegador del dispositivo apuntando a la página web de la película en el IMDB:

La Parábola del Pescador y el Ejecutivo MBA de Harvard

Una vez, un poderoso Ejecutivo se fue de vacaciones – su primeras vacaciones en 15 años. Mientras exploraba un muelle en una pequeña villa pesquera, un Pescador de tuna desembarcaba y acoplaba su bote. Mientras el Pescador amarraba su bote al muelle, el Ejecutivo lo felicitó por el tamaño y la calidad del pez que había atrapado.

“¿Cuánto tiempo le tomó atrapar ese pez? “, preguntó el Ejecutivo.“Solo un poco de tiempo.”, dijo el Pescador.“¿Por qué no se queda más tiempo y atrapa más? “, preguntó el Ejecutivo.“Tengo lo suficiente para soportar las necesidades de mi familia.”, dijo el Pescador.“Pero, ” – preguntó el Ejecutivo – “¿Qué hace con el resto de su tiempo? “
El pescador respondió, “Duermo hasta tarde, pesco un poco, juego con mis hijos, tomo un siesta con mi esposa, paseo en la villa cada tarde, donde tomo un poco de vino y toco guitarra con mis amigos. Tengo una agenda llena y una vida ocupada.”

El Ejecutivo estaba asombrado, le dijo, “Yo soy un MBA graduado de Harvard, y puedo ayudarle. Usted debe emplear más tiempo pescando. Con las ganancias, usted puede comprar un bote más grande. Un bote más grande le permitirá atrapar más peces, los que podrá vender para comprar más botes. Eventualmente poseerá una flotilla entera”

“En vez de vender lo que atrapa a un intermediario puede venderlos directamente a los consumidores, con lo cual puede mejorar sus márgenes de beneficio. Eventualmente, usted puede abrir su propia factoría, teniendo un control del producto, el procesamiento, y la distribución. Por supuesto, tendrá que dejar la villa y moverse a ls ciudad de forma tal que pueda dirigir su empresa en expansión.”

El Pescador se quedo quieto por un momento, entonces preguntó, “¿Qué tanto tiempo me tomaría?”“Entre 15 y 20 años. 25 como máximo … “, el Ejecutivo respondió.“¿Y entones que?”, preguntó el Pescador.El Ejecutivo se rió. “Esa es la mejor parte. Cuando el momento sea el adecuado, hará su compañía pública y venderá acciones de ella. Hará millones. ““¿Millones?¿Y qué hago entones?”, preguntó el Pescador.

El Ejecutivo pausó por un momento. “Usted se puede retirar, dormir hasta tarde, pescar un poco, jugar con sus hijos, tomar un siesta con su esposa, pasear en la villa cada tarde, tomar un poco de vino y tocar guitarra con sus amigos.”
Sacudiendo su cabeza, el Ejecutivo se despidió cordialmente. Inmediatamente desde que regresó de sus vacaciones, el Ejecutivo renunció de su posición.