Tuesday, July 3, 2012

Android widget tutorial

Android widgets are small application which can be embedded inside another application such as Home screen. It can be used to show updated data to the user. You can embed these widgets in  an application. These widgets get updated periodically based either on their periodicity set in the widget xml configuration file or by the use of Alarm-manager. Widgets can also be updated asynchronously from Broadcast receiver. The tutorial given below explains the procedure to update the widget from broadcast receiver. But before that you must be aware of certain basics about widget. You can also find the related documentation here.

Basic building blocks

  • AppWidgetProviderInfo object
  • AppWidgetProvider 
  • Widget View layout 

AppWidgetProviderInfo object:

It provides meta-data about the widget for e.g widget initial layout, its min-height, min-width, update frequency and AppWidgetProvider class. It should be defined in xml file.

AppWidgetProvider:

It provides interfaces and ways to interact with the widget. The callback methods get called based on the broadcast events. These events get triggered as soon as the widget is added, removed, enabled,  disabled or update interval lapses.

Widget ViewLayout:

View layout is basically written in the xml file. This is the initial layout of the widget. There are very  few View components available. The RemoteView Object can support the following layout classes:
  • FrameLayout
  • GridLayout (Available on API 16)
  • LinearLayout
  • RelativeLayout
and following widget classes
  • AnalogClock
  • Button
  • Chronometer
  • FrameLayout
  • ImageButton
  • ImageView
  • ProgressBar
  • TextView
  • ViewFlipper
  • ViewStub (Available on API 16)
Project Information:  Meta-information about the project.
Platform Version : Android API Level 10.
IDE : Eclipse Helios Service Release 2
Emulator: Android 4.0.4
Prerequisite: Preliminary knowledge of Android application framework, Intent Broadcast receiver and Service.

Now create project by Eclipse > File> New Project>Android Application Project. The following dialog box will appear. Fill the required field, i.e Application Name, Project Name and Package Name. Don't forget to select the Build SDK version (for this tutorial Google API 10 has been selected). Now press the next button.


Once the dialog box appears, select the BlankActivity and click the next button.

Fill the Activity Name and Layout file name for the dialog box shown below and hit the finish button.

This process will setup the basic project files. The next step is to define the widget layout xml file. The  same can be created with the help of Android XML file generator dialog window which can be  accessed through these menu and sub-menu options Eclipse>File>new>Android XML File. Since we have to create Layout file so we need to select Layout for Resource Type. The project field is automatically populated with project name. In File field enter the suitable xml file name. Don't forget to put the ".xml" file extension otherwise the dialog window will show error. In the root element list select the layout  for the widget. For this tutorial Linear Layout has been selected as shown in the picture below.

Content of widget_layout.xml file. You can ignore the "android:background" attribute field for LinearLayout and button, since this field has been used here to customize the background color and the size.
 <linearlayout android:background="@drawable/background"
       android:gravity="center" android:layout_height="match_parent" 
       android:layout_margin="4dp" android:layout_width="match_parent"  
       android:orientation="vertical"   
       xmlns:android="http://schemas.android.com/apk/res/android">
   <button android:background="@drawable/button_shape" 
     android:id="@+id/BtEnableDisable" 
     android:layout_height="wrap_content" android:layout_width="100dp" 
     android:text="@string/strWidgetBtEnableDisable">
</button></linearlayout>

The next step is to define widget meta data. This xml file basically represents the AppWidgetProviderInfo object. As mentioned earlier this file contains information about the widget for e.g min-width, min-height, update frequency etc. Here the update frequency has been set as 0 because we want to asynchronously update the widget. The minimum frequency allowed by Android system is 30min(30X60X100 miliseconds). If the update frequency is less than 180,000 miliseconds Android may ignore  this value. This file also contains the widget layout file name which is widget_background.xml in this case. For creating this file you can use the Android XML file dialog as explained in the above steps. But make sure that you select the Resource Type as AppWiget Provider. The content of widget_metadata.xml file is as following. 
 
<appwidget-provider android:initiallayout="@layout/widget_layout" 
     android:minheight="40dp" android:minwidth="130dp" 
     android:updateperiodmillis="0" 
     xmlns:android="http://schemas.android.com/apk/res/android">
</appwidget-provider>
In order to customize the widget background and shape you need to define the xml and include it in widget_layout.xml file. To create this file use Android XML file dialog as mentioned in the previous steps but for customization file select Resource Type as Drawable and Root element as Shape. You can put the following content in background.xml.
  <shape android:id="@+id/Corners" 
           xmlns:android="http://schemas.android.com/apk/res/android">
    <gradient android:angle="45" android:endcolor="#997f7f7f"
      android:startcolor="#99111111">
       <padding android:bottom="1dp" android:left="4dp"
             android:right="4dp" android:top="1dp">
        <corners android:radius="7dp">
          <stroke android:color="#FFFFFFFF" android:width="2dp">
          </stroke>
        </corners>
      </padding>
    </gradient>
</shape>

If you want to customize the button then you should define another customization file for button and include that in layout file. If you are not keen to customize the button or widget then go to the next step. Content of button_shape.xml
  <shape android:id="@+id/shapeBt" 
          xmlns:android="http://schemas.android.com/apk/res/android">
   <gradient android:angle="45" android:endcolor="#FFFAFAFA" 
           android:startcolor="#FFFFFFFF">
    <padding android:bottom="1dp" android:left="4dp" 
          android:right="4dp" android:top="1dp">
     <corners android:radius="7dp">
      <stroke android:color="#FFFFFFFF" android:width="2dp">
        <size android:height="35dp" android:width="90dp">
        </size>
      </stroke>
     </corners>
   </padding>
 </gradient>
</shape>
SimpleWidgetAppWigetProvider class extends AppWidgetProvider class. The methods defined in the SimpleWidgetAppWidgetProvider determine the behavior of the the widget. These methods get called based on the events triggered.
Methods present in AppWidgetProvider:

  • onReceive(): This method gets called for every broadcast and before every other callback method present in AppWidgetProvider. In most of the cases you don't need to implement this method. 
  • onEnabled(): This method gets called at the very first instance when the widget gets created. This method is similar to onCreate() method present in Activity class. If some initialization is required for your widget then  this is the right place to do it.
  • onDisabled(): This method gets called when the very last instance is removed from the container.
  • onUpdate(): This method gets called to update the widget. It gets called even when you create an instance of widget, thus it makes some initial setup. This method gets called at interval defined by updatePeriodMillis attribute in widget-meta info.  
  • onDeleted(): As the name suggests, this method gets called whenever an instance is removed/deleted.
You will notice that Service gets invoked by startService()  method call and passing intent object as  a parameter in startService().

package com.rakesh.simplewidget;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;


public class SimpleWidgetAppWidgetProvider extends AppWidgetProvider {

 public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {

  final int N = appWidgetIds.length;
  ComponentName thisWidget = new ComponentName(context,
    SimpleWidgetAppWidgetProvider.class);
  int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);

  // Build the intent to call the service
  Intent intent = new Intent(context.getApplicationContext(),
    UpdateWidgetService.class);
  intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, allWidgetIds);

  // Update the widgets via the service
  context.startService(intent);
}

UpdateWidgetService class extends the service class. Here onStart() method has been defined which  does the actual job(here we are trying to enable/disable the packet data) and then it tries to update the remote view with the help of updateAppWidget() method call. PendingIntent and setOnClickPendingIntent() method help to attach action listener to the button.
package com.rakesh.simplewidget;


import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.telephony.TelephonyManager;
import android.widget.RemoteViews;

public class UpdateWidgetService extends Service {

 @Override
 public void onStart(Intent intent, int startId) {

  AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this
    .getApplicationContext());

  int[] allWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);

  ComponentName thisWidget = new ComponentName(getApplicationContext(),
    SimpleWidgetAppWidgetProvider.class);

  for (int widgetId : allWidgetIds) {
   RemoteViews remoteViews = new RemoteViews(this
     .getApplicationContext().getPackageName(),
     R.layout.widget_layout);

   EnableDisableConnectivity edConn = new EnableDisableConnectivity(this.getApplicationContext());
   edConn.enableDisableDataPacketConnection(!checkConnectivityState(this.getApplicationContext()));

   // Register an onClickListener
   Intent clickIntent = new Intent(this.getApplicationContext(),
     SimpleWidgetAppWidgetProvider.class);

   clickIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
   clickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS,
     allWidgetIds);

   PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, clickIntent,
     PendingIntent.FLAG_UPDATE_CURRENT);
   remoteViews.setOnClickPendingIntent(R.id.BtEnableDisable, pendingIntent);
   appWidgetManager.updateAppWidget(widgetId, remoteViews);
  }
  stopSelf();

  super.onStart(intent, startId);
 }

 @Override
 public IBinder onBind(Intent intent) {
  return null;
 }

 private boolean checkConnectivityState(Context context){
  final TelephonyManager telephonyManager = (TelephonyManager) context
    .getSystemService(Context.TELEPHONY_SERVICE);
  return telephonyManager.getDataState() == TelephonyManager.DATA_CONNECTED;

 }
}

Since this tutorial is about how to update the widget when CONNECTIVITY_CHANGE intent is received, so here ConnectivityReceiver class has been defined which extends the BroadcastReceiver class. When application receives CONNECTIVITY_CHANGE intent it calls onReceive() method. In onReceive() method we try to get the remote view and then change the text and color of the button with the help of setTetViewText() and setTextColor() method respectively.

package com.rakesh.simplewidget;
import android.appwidget.AppWidgetManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.Toast;


public class ConnectivityReceiver extends BroadcastReceiver {

 @Override
 public void onReceive(Context context, Intent intent) {
  NetworkInfo info = (NetworkInfo)intent.getExtras().get(ConnectivityManager.EXTRA_NETWORK_INFO);
  
  if(info.getType() == ConnectivityManager.TYPE_MOBILE){
   
   RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
     R.layout.widget_layout);
   
   if(info.isConnectedOrConnecting()){
    Toast.makeText(context, "Data packet enabled", Toast.LENGTH_SHORT).show();
    Log.d("RK","Mobile data is enabled");
    remoteViews.setTextColor(R.id.BtEnableDisable, Color.GREEN);
    remoteViews.setTextViewText(R.id.BtEnableDisable, "Enabled");
   }else{
    Toast.makeText(context, "Data packet disabled", Toast.LENGTH_SHORT).show();
    Log.e("RK","Mobile data is disconnected");
    remoteViews.setTextColor(R.id.BtEnableDisable, Color.BLACK);
    remoteViews.setTextViewText(R.id.BtEnableDisable,"Disabled");
   }
   
   ComponentName thiswidget = new ComponentName(context, SimpleWidgetAppWidgetProvider.class);
   AppWidgetManager manager = AppWidgetManager.getInstance(context);
   manager.updateAppWidget(thiswidget, remoteViews);
   
  }
 }

}
Below is the Android manifest file which defines the application components and their permissions.
<manifest android:versioncode="1" android:versionname="1.0" 
      package="com.rakesh.simplewidget"  
      xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-sdk android:minsdkversion="10">
    
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE">
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE">

    &t;application android:icon="@drawable/widget_icon" 
            android:label="@string/app_name">
        <activity android:label="@string/app_name" 
              android:name=".SimpleWidgetExampleActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN">

                <category android:name="android.intent.category.LAUNCHER">
            </category></action></intent-filter>
        </activity>
        
        <receiversxml android:icon="@drawable/widget_icon"
             android:label="@string/app_name" 
             android:name=".SimpleWidgetAppWidgetProvider">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE">
            </action></intent-filter>
            <meta-data android:name="android.appwidget.provider" 
                 android:resource="@xml/widget_metadata">
        

        <service android:name=".UpdateWidgetService">
        <receiver android:name=".ConnectivityReceiver">
            <intent-filter>
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE">
            </action></intent-filter>
        </receiver>
    </service></meta-data></receiversxml></application>

     </uses-permission>
     </uses-permission>
   </uses-sdk>
</manifest>

You may have noticed that the AppwidgetProvider class has registered APPWIDGET_UPDATE intent. The class shown below is a helper class which basically enables/disables the packet data connection state. You can skip this part because it does not have much relevance to widget. But I have provided this code for those who may be interested in enabling/disabling packet data connection.

package com.rakesh.simplewidget;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import android.content.Context;
import android.net.ConnectivityManager;
import android.telephony.TelephonyManager;
import android.util.Log;

public class EnableDisableConnectivity {
 private Context mContext;
 public EnableDisableConnectivity(Context context){
  this.mContext = context;
 }
 public boolean enableDataPacketConnection(){
  return enableDisableDataPacketConnection(true);
 }
 public boolean disableDataPacketConnection(){
  return enableDisableDataPacketConnection(false);
 }

 public boolean enableDisableDataPacketConnection(boolean enable){
  boolean result = false;
  Method dataConnSwitchmethod;
  Class telephonyManagerClass;
  Object ITelephonyStub;
  Class ITelephonyClass;
  final ConnectivityManager conman = 
         (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
  try{
   final Class conmanClass = Class.forName(conman.getClass().getName());
   final Field iConnectivityManagerField = conmanClass.getDeclaredField("mService");
   iConnectivityManagerField.setAccessible(true);
   final Object iConnectivityManager = iConnectivityManagerField.get(conman);
   final Class iConnectivityManagerClass = Class.forName(iConnectivityManager.getClass().getName());
   final Method setMobileDataEnabledMethod = 
      iConnectivityManagerClass.getDeclaredMethod("setMobileDataEnabled", Boolean.TYPE);
   setMobileDataEnabledMethod.setAccessible(true);
   setMobileDataEnabledMethod.invoke(iConnectivityManager, enable);
   result = true;
  }catch(Exception e){
   Log.e("Error", "here is an exception "+e.getMessage());
   result =false;
  }
  return result;
 }

 private boolean checkConnectivityState(){
  final TelephonyManager telephonyManager = (TelephonyManager) mContext
    .getSystemService(Context.TELEPHONY_SERVICE);
  return telephonyManager.getDataState() == TelephonyManager.DATA_CONNECTED;

 }
}

On executing this code you will notice that one icon named Data packet widget has been deployed     and appears in the application list as shown in Fig 1. If you want to check whether widget has been recognized by Android  ,then you need to check the widgets tab(Fig. 2) on Android 15. For Android version lower than 15, you need to long-press on the home-screen and then you will see the list of widgets available. Select the  data-packet-widget it will get deployed on home-screen. Once the widget is deployed you will see the widget on home-screen as shown in Fig 3.
Application tab which shows installed applications.
1. Applications tab
Widgets tab shows what all widgets are available.
2. Widgets tab
Home screen which shows that Data-packet widget is installed on  home screen.
3. Installed widget
If you want to explore the new features of widgets introduced in Android 4.1, watch the video below.

If you want to download the source code you can find it here. In next Widget tutorial we will learn how to use AlarmManger to update the widget if the periodicity of update is less than 30 mins.

Related tutorial:

Tutorial on Android Widget with AlarmManager
Tutorial on Android AlaramManager

Your valuable comments are always welcomed. It will help to improve my post and understanding.
"By three methods we may learn wisdom: First, by reflection, which is noblest; Second, by imitation, which is easiest; and third by experience, which is the bitterest."
By : Confucius