time

Creating a home screen Widget for Android

Creating a home screen Widget for Android

Introduction

Widgets can be thought of as a small window or controller for an Android app that can be embedded in another application (like the Home screen). They can be very useful, allowing users to view or control an app without actually launching it. Also it can notify users about application updates. They are accessible right from user home screens on Android devices. The great thing about widgets is that they can be updated automatically (after a time period), or in response to user action.

There are several types of widgets for Android:

  • Information widgets. They display information elements that are most crucial for the user. Essentially the majority of widgets belong to this type.
  • Collection widgets. They display collections of either articles, or emails, or pictures. They are scrollable and when clicked - pull the user to the app.
  • Control widgets. They are remote controls of the main functions of the app. For example, the play button on the music app widget.
  • Hybrid widgets. They combine the elements of different other widgets in one.

So, an AppWidget is a view element that is designed to run in the Launcher application’s process and show periodic updates of the data but is controlled from your application’s process. Because of this, special pieces of the framework that are designed to support remote process connections must be used. In particular, the view hierarchy of the widget must be provided wrapped in a RemoteViews object, which has methods to update view elements by ID without needing to gain direct access to them. RemoteViews supports only a subset of the layouts and widgets in the framework. The following list shows what RemoteViews supports currently:

  • FrameLayout
  • LinearLayout
  • RelativeLayout
  • GridLayout

And the following widgets:

  • AnalogClock
  • Button
  • Chronometer
  • ImageButton
  • ImageView
  • ProgressBar
  • TextView
  • ViewFlipper
  • ListView
  • GridView
  • StackView
  • AdapterViewFlipper

The view for your AppWidget must be composed of these objects only, or the view will not properly display.

Widget use RemoteViews to create their user interface. A RemoteView can be executed by another process with the same permissions as the original application. This way the widget runs with the permissions of its defining application.

Working in a remote process also means that most user interaction must be handled through PendingIntent instances, rather than traditional listener interfaces. The PendingIntent allows your application to freeze the intent action along with the Context that has permission to execute it; this is so the action can be freely handed off to another process and be run at the specified time as if it had come directly from the originating application Context.

Android Launcher screens on handsets are typically made from a 4×4 grid of spaces in which you can fit your AppWidget. While tablets will have considerably greater space, this should be the design metric to keep in mind when determining the minimum height or width of your widget. Android 3.1 introduced the ability for a user to also resize an AppWidget after it had been placed, but prior to that, a widget's size was fixed to these values. Taken from the Android documentation, following table defines a good rule of thumb to use in determining how many cells a given minimum size will occupy.

 Number of cells   Available space 
1 40dp
2 110dp
3 180dp
4 250dp
n (70 × n) – 30

So, as an example, if your widget needed to be at least 200dp x 48dp in size, it would require three columns and one row in order to display on the Launcher.

The life cycle of a widget has the following phases:

  1. Widget definition.
  2. Widget instance creation.
  3. onUpdate() (when the time interval expires).
  4. Responses to clicks (on the widget view on the home screen).
  5. Widget deletion (from the home screen).
  6. Uninstallation.

Steps to create a widget:

  1. Define a layout file.
  2. Create an XML file (AppWidgetProviderInfo) which describes the properties of the widget, e.g. size or the fixed update frequency.
  3. Create a BroadcastReceiver which is used to build the user interface of the widget.
  4. Enter the widget configuration in the AndroidManifest.xml file.
  5. Optional you can specify a configuration activity which is called once a new instance of the widget is added to the widget host.

When a user chooses a widget to create a widget instance, Android invokes the configuration activity if it is defined in the configuration XML file for the widget. If this configuration activity is not defined then this phase skipped and the widget is presented directly on the home page. When invoked this configuration activity does the following:

  1. Receive the widget instance ID from the invoking intent that started the configuration activity.
  2. Prompt the user through form fields to collect the widget-instance–specific information.
  3. Persist the widget instance information so that subsequent calls to AppWidgetProvider’s onUpdate method have access to this information.
  4. Prepare to display the widget view for the first time by retrieving the widget view layout and create a RemoteViews object with it.
  5. Call methods on the RemoteViews object to set values on individual view objects, such as text and images.
  6. Also use the RemoteViews object to register any onClick events on any of the subviews of the widget.
  7. Tell the AppWidgetManager to paint the RemoteViews on the home screen using the instance ID of that widget.
  8. Return the widget ID, and close.

Before we dig in to the code for creating an AppWidget, let's cover the basics. There are three required and one optional component:

  • The AppWidgetProviderInfo file. An XML file containing important information about your widget, such as its minimum width and height, its layout resource file, how often it should be updated, and whether it uses a configuration Activity.
  • The AppWidgetProvider class. This is where you’ll define the methods that you’ll use to programmatically interact with your widget.
  • The View layout file. It's a standard layout XML file, with some restrictions.
  • The App Widget configuration Activity (optional). This Activity launches when placing the widget to set configuration options.

Since the AppWidgetProvider is a helper class based on the Broadcast Receiver, it is declared in the AndroidManifest file with the <receiver> element. Here is an example manifest entry:

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

The meta-data points to the AppWidgetProviderInfo file, which is placed in the res/xml directory. Here is a sample AppWidgetProviderInfo.xml file:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:updatePeriodMillis="1800000"
    android:previewImage="@drawable/preview_image"
    android:initialLayout="@layout/appwidget"
    android:configure="me.proft.sandbox.AppWidgetConfiguration"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen">
</appwidget-provider>

Here's a brief overview of the available attributes:

  • minWidth: the default width when placed on the Home screen.
  • minHeight: the default height when placed on the Home screen.
  • updatePeriodMillis: it's part of onUpdate() polling interval (in milliseconds). Be judicious with this value, and do not set it higher than you need to. In many cases, it is more efficient to have other services or observers notifying you of changes that require an AppWidget update. In fact, Android will not deliver updates to an AppWidget more frequently than 30 minutes (1800000 milliseconds).
  • initialLayout: the AppWidget layout.
  • previewImage (optional): the image shown when browsing AppWidgets.
  • configure (optional): provides an activity that should be launched to configure the AppWidget before it is added to the Launcher.
  • resizeMode (optional): the flags indicate resizing options - horizontal, vertical, none.
  • minResizeWidth (optional): the minimum width allowed when resizing.
  • minResizeHeight (optional): the minimum height allowed when resizing
  • widgetCategory (optional): Android 5+ only supports Home screen widgets.

The AppWidgetProvider class is where you’ll define the methods that will be called during the widget’s lifecycle, for example whenever a widget is deleted, enabled or disabled. This class is also where you’ll create the code that'll ultimately be responsible for updating your widget. The AppWidgetProvider extends the BroadcastReceiver class, which is why <receiver> is used when declaring the AppWidget in the Manifest. As it's BroadcastReceiver, the class still receives the OS broadcast events, but the helper class filters those events down to those applicable for an AppWidget. The AppWidgetProvider class exposes the following methods:

  • onUpdate(): it's called when initially created and at the interval specified.
  • onAppWidgetOptionsChanged(): it's called when initially created and any time the size changes.
  • onDeleted(): it's called any time a widget is removed.
  • onEnabled(): it's called the first time a widget is placed (is not called when adding a second and subsequent widgets).
  • onDisabled(): it's called when the last widget is removed.
  • onReceive(): it's called on every event received, including the preceding event. Usually not overridden as the default implementation only sends the applicable events.
  • onAppWidgetOptionsChanged(): this method gets called when the widget is resized.

In Android 4.1, a new feature has been introduced for Homescreen Widget which enables widget to reorganize its view when resized. To support this feature a new method onAppWidgetOptionsChanged() has been introduced in AppWidgetProvider class. This method gets called in response to the ACTION_APPWIDGET_OPTIONS_CHANGED broadcast when this widget has been layed out at a new size.

Because AppWidgetProvider is a BroadcastReceiver, it is not considered good practice to do long operations inside of it. If you must do intensive work to set up your AppWidget, you should start a service instead and perhaps a background thread as well to do the work

public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    // start the background service to update the widget
    context.startService(new Intent(context, SomeService.class));
}

For convenience, this method is passed an AppWidgetManager instance, which is necessary for updating the AppWidget if you do so from this method. It is also possible to have multiple AppWidgets loaded on a single Launcher screen. The array of IDs references each individual AppWidget so you can update them all at once.

Internally, Android keeps track of the widget instances by allocating them unique IDs. This unique widget instance ID is passed to the callbacks and to the configurator class so that initial configuration and updates can be directed to the right instance of the widget on the homepage.

This is what you typically need to do in an onUpdate() method:

  1. Make sure the configurator (Activity) has finished its work; otherwise, just return. This should not be problem in releases 2.0 and above, where the duration is expected to be longer. Otherwise, based on the update interval (when it is too small) it is possible that onUpdate() will be called before the user has finished configuring the widget in the configurator.
  2. Retrieve the persisted data for that widget instance.
  3. Retrieve the widget view layout, and create a RemoteViews object with it.
  4. Call methods on the RemoteViews to set values on individual view objects such as text and images.
  5. Register any onClick events on any of the views by using pending intents.
  6. Tell the AppWidgetManager to paint the updated RemoteViews using the instance ID.

We'll start by creating the widget layout, which resides in the standard layout resource directory. Then we'll create the xml resource directory to store the AppWidgetProviderInfo file. We'll add a new Java class and extend AppWidgetProvider , which handles the onUpdate() call for the widget. With the receiver created, we can then add it to the Android Manifest.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="0dp"
    android:orientation="vertical">

    <!-- IMAGE -->

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="0px"
        android:layout_weight="0.85"
        >

        <ImageView
            android:id="@+id/ivImage"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/nature1"
            android:scaleType="centerCrop"
            />

        <ImageView
            android:id="@+id/swipe_left"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_centerVertical="true"
            android:src="@drawable/swipe_left" />

        <ImageView
            android:id="@+id/swipe_right"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:src="@drawable/swipe_right" />
    </RelativeLayout>

    <!-- CLOCK -->

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0px"
        android:orientation="horizontal"
        android:layout_weight="0.15"
        android:paddingLeft="10dp"
        android:background="@drawable/widget_bg">

        <TextView
            android:id="@+id/tvDate"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Date"
            android:textSize="20dp"
            android:textColor="@android:color/white"
            android:gravity="left"
            android:layout_marginLeft="2dp"
            />

        <TextView
            android:id="@+id/tvTime"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Time"
            android:textSize="20dp"
            android:textColor="@android:color/white"
            android:gravity="right"
            android:layout_marginRight="6dp"
            />
    </LinearLayout>
</LinearLayout>

As you can see I use some decoration for LinearLayout with date and time. Create widget_bg.xml in res/drawable with following content.

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <gradient
        android:angle="90"
        android:startColor="#FF449EDA"
        android:endColor="#FFC6E6FF"
        android:type="linear"
        />
    <corners
        android:bottomRightRadius="5dip"
        android:bottomLeftRadius="5dp"
        />
    <padding
        android:left="0dip"
        android:top="0dip"
        android:right="0dip"
        android:bottom="0dip" />
</shape>

Create a new file in res/xml called appwidget_info.xml using the following xml:

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_layout"
    android:minWidth="250dp"
    android:minHeight="180dp"
    android:updatePeriodMillis="0"
    android:previewImage="@drawable/nature1"
    android:widgetCategory="home_screen">
</appwidget-provider>

The updatePeriodMillis attribute sets the update frequency. Since the update will wake up the device, it's a trade-off between having up-to-date data and battery life. In this example updatePeriodMillis=0 it means no updates via AppWidget approach. We'll use AlarmManager for time update.

Create a new Java class called HomescreenWidgetProvider extending AppWidgetProvider. The AppWidgetProvider class is where we handle the onUpdate() event triggered by the updatePeriodMillis polling.

public class HomescreenWidgetProvider extends AppWidgetProvider {
    String TAG = "WIDGET";
    int TIMER_ID = 12345;
    private static final String onLeftSwipe = "onLeftSwipe";
    private static final String onRightSwipe = "onRightSwipe";
    private static final String onImageClick = "onImageClick";

    int[] images = {R.drawable.nature1, R.drawable.nature2, R.drawable.nature3};

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        ComponentName thisWidget = new ComponentName(context, HomescreenWidgetProvider.class);

        Format formatterTime = new SimpleDateFormat("HH:mm");
        String time = formatterTime.format(new Date());

        Format formatterDate = new SimpleDateFormat("dd.MM.yy E");
        String date = formatterDate.format(new Date());

        for (int id : appWidgetManager.getAppWidgetIds(thisWidget)) {
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
            remoteViews.setTextViewText(R.id.tvTime, time);
            remoteViews.setTextViewText(R.id.tvDate, date);

            remoteViews.setOnClickPendingIntent(R.id.swipe_left, 
                getPendingSelfIntent(context, onLeftSwipe));

            remoteViews.setOnClickPendingIntent(R.id.swipe_right, 
                getPendingSelfIntent(context, onRightSwipe));

            remoteViews.setOnClickPendingIntent(R.id.ivImage, 
                getPendingSelfIntent(context, onImageClick));

            appWidgetManager.updateAppWidget(id, remoteViews);
        }
    }

    protected PendingIntent getPendingSelfIntent(Context context, String action) {
        Intent intent = new Intent(context, getClass());
        intent.setAction(action);
        return PendingIntent.getBroadcast(context, 0, intent, 0);
    }

    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);

        int pos = 0;
        SharedPreferences sp = context.getSharedPreferences(TAG, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sp.edit();

        if (sp.contains("pos")) {
            pos = sp.getInt("pos", 0);
        } else {
            editor.putInt("pos", 0);
            editor.commit();
        }

        Intent intentWidgetUpdate = new Intent(context, WidgetUpdateReceiver.class);
        PendingIntent sender = PendingIntent.getBroadcast(context, TIMER_ID, 
            intentWidgetUpdate, PendingIntent.FLAG_NO_CREATE);

        if (sender == null)
            startAlarm(context);

        if (onLeftSwipe.equals(intent.getAction())){
            if (pos > 0) {
                pos = pos - 1;
            }
        } else if (onRightSwipe.equals(intent.getAction())) {
            if (pos < images.length - 1) {
                pos = pos + 1;
            }
        } else if (onImageClick.equals(intent.getAction())) {
            Toast.makeText(context, images[pos], Toast.LENGTH_SHORT).show();
        }

        ComponentName thisWidget = new ComponentName(context, HomescreenWidgetProvider.class);
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

        for (int id : appWidgetManager.getAppWidgetIds(thisWidget)) {
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(), 
                R.layout.widget_layout);

            remoteViews.setImageViewResource(R.id.ivImage, images[pos]);

            appWidgetManager.updateAppWidget(id, remoteViews);
        }

        editor.putInt("pos", pos);
        editor.commit();
    };

    @Override
    public void onDisabled(Context context) {
        Intent intent = new Intent(context, WidgetUpdateReceiver.class);
        PendingIntent sender = PendingIntent.getBroadcast(context, TIMER_ID, intent, 0);
        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(sender);
        Toast.makeText(context, "HomescreenWidgetProvider onDisabled()", Toast.LENGTH_SHORT).show();
        Log.d(TAG, "onDisabled()");
        super.onDisabled(context);
    }

    @Override
    public void onEnabled(Context context) {
        super.onEnabled(context);
        Toast.makeText(context, "HomescreenWidgetProvider onEnabled()", Toast.LENGTH_SHORT).show();
        startAlarm(context);
    }

    public void startAlarm(Context context) {
        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(context, WidgetUpdateReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(context, TIMER_ID, intent, 0);
        long interval = 60000; // 60 secs

        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.MINUTE, 1);
        cal.set(Calendar.SECOND, 0);
        long afterOneMinutes = cal.getTimeInMillis();

        am.setRepeating(AlarmManager.RTC, afterOneMinutes, interval , pi);
    }
}

As discussed earlier, an AppWidget is a RemoteView. Therefore, to get the layout, we call RemoteViews() with our fully qualified package name and the layout ID. Once we have the layout, we can attach the pending intent to the button views using setOnClickPendingIntent(). We call the AppWidgetManager named updateAppWidget() to initiate the changes we made.

Following is BroadcastReceiver for time and date update.

public class WidgetUpdateReceiver extends BroadcastReceiver {
    String TAG = "WIDGET";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "onReceive in WidgetUpdateReceiver");
        Format formatterTime = new SimpleDateFormat("HH:mm");
        String time = formatterTime.format(new Date());

        Format formatterDate = new SimpleDateFormat("dd.MM.yy E");
        String date = formatterDate.format(new Date());

        RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
        remoteViews.setTextViewText(R.id.tvTime, time);
        remoteViews.setTextViewText(R.id.tvDate, date);

        ComponentName widget = new ComponentName(context, HomescreenWidgetProvider.class);
        AppWidgetManager manager = AppWidgetManager.getInstance(context);
        manager.updateAppWidget(widget, remoteViews);
    }
}

Add HomescreenWidgetProvider to AndroidManifest using the following XML declaration within the <application> element:

<application
    ...>

    <receiver android:name=".HomescreenWidgetProvider">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            <action android:name="android.appwidget.action.APPWIDGET_ENABLED" />
            <action android:name="android.appwidget.action.APPWIDGET_DELETED" />
            <action android:name="android.appwidget.action.APPWIDGET_DISABLED" />
        </intent-filter>

        <meta-data
            android:name="android.appwidget.provider"
            android:resource="@xml/appwidget_info" />
    </receiver>

    <receiver android:name=".WidgetUpdateReceiver"/>

</application>

Run the program on a device or emulator. After first running the application, the widget will then be available to add to the Home screen.

Result

android_widget.png

You can get images for this project here.

Also I disabled MainActivity as launch activity. For this, go to Run - Edit configurations... - in Launch Options select Nothing.

Update widget via a service

The following will demonstrate the usage of a service to update the widget. With auto update feature, your widget become lively and useful!

Create the following UpdateWidgetService class in your project.

public class UpdateWidgetService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String time = getCurrentDateTime();

        RemoteViews view = new RemoteViews(getPackageName(), R.layout.widget_layout);
        view.setTextViewText(R.id.tvTime, time);
        ComponentName theWidget = new ComponentName(this, HomescreenWidgetProvider.class);
        AppWidgetManager manager = AppWidgetManager.getInstance(this);
        manager.updateAppWidget(theWidget, view);
        return super.onStartCommand(intent, flags, startId);
    }

    private String getCurrentDateTime() {
        Calendar c = Calendar.getInstance();
        int minute = c.get(Calendar.MINUTE);
        int hour = c.get(Calendar.HOUR_OF_DAY);
        return hour + ":" + minute;
    }
}

Add this class as a Service to your AndroidManifest.xml file.

<service android:name=".UpdateWidgetService"></service>

The layout for this widget:

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

    <TextView
        android:id="@+id/tvTime"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_margin="8dp"
        android:background="#09C"
        android:text="Time"
        android:textColor="#ffffff"
        android:textSize="24sp"
        android:textStyle="bold|italic"/>
</RelativeLayout>

And define the AppWidgetProviderInfo object in an XML like this:

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_layout"
    android:minHeight="80dp"
    android:minWidth="80dp"
    android:previewImage="@mipmap/ic_launcher"
    android:resizeMode="vertical"
    android:updatePeriodMillis="0">
</appwidget-provider>

Service does not start by itself. We need to start the service (in every minute for this example) in the AppWidgetProvider. But why we do not just use updatePeriodMillis atribute? The official document say that:

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").
public class HomescreenWidgetProvider extends AppWidgetProvider {
    private PendingIntent pendingIntent;

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        final AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        final Intent i = new Intent(context, UpdateWidgetService.class);

        if (pendingIntent == null) {
            pendingIntent = PendingIntent.getService(context, 0, i, PendingIntent.FLAG_CANCEL_CURRENT);
        }
        manager.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), 
            60000, pendingIntent);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
    }
}

The minimum interval time is 60000 milliseconds for AlarmManager.

How to check is widget placed on Android screen

int ids[] = AppWidgetManager.getInstance(this).getAppWidgetIds(new ComponentName(this, 
    WidgetProvider.class));
Toast.makeText(this, "Number of widgets: " + ids.length, Toast.LENGTH_LONG).show();

How to call Activit from widget

Following example shows how to call MainActivity activity.

public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    super.onUpdate(context, appWidgetManager, appWidgetIds);
    for (int count=0; count < appWidgetIds.length; count++) {
        RemoteViews appWidgetLayout = new RemoteViews(context.getPackageName(), R.layout.widget);
        Intent intent = new Intent(context, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
        appWidgetLayout.setOnClickPendingIntent(R.id.analogClock, pendingIntent);
        appWidgetManager.updateAppWidget(appWidgetIds[count], appWidgetLayout);
    }
}

Open url on button click.

public void onUpdate(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
    super.onUpdate(context, appWidgetManager, appWidgetIds);

    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_simple);

    // create an Intent object includes my website address
    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://en.proft.me/"));
    PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);

    // handle click event of the TextView
    views.setOnClickPendingIntent(R.id.tvOpen, pendingIntent);
    appWidgetManager.updateAppWidget(appWidgetId, views);
}

How to save some data inside widget

I usually use the application preferences, but you could use anything. Generally widgets use services to communicate, so your code that does stuff is likely in a service, but using the preference allows any portion of your app to access this.

In your widget class that extends AppWidgetProvider the onEnabled is called when the widget is put on a homescreen and the onDeleted is called when it's removed. onDisabled is called when all copies are removed.

So in the code of your widget provider:

@Override
public void onEnabled(Context context) {
    super.onEnabled(context);
    setWidgetActive(true);
    context.startService(new Intent(appContext, WidgetUpdateService.class));
}

@Override
public void onDisabled(Context context) {
    Context appContext = context.getApplicationContext();
    setWidgetActive(false);
    context.stopService(new Intent(appContext, WidgetUpdateService.class));
    super.onDisabled(context);
}

private void setWidgetActive(boolean active){
    Context appContext = context.getApplicationContext();
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
    SharedPreferences.Editor edit = prefs.edit();
    edit.putBoolean(Constants.WIDGET_ACTIVE, active);
    edit.commit();
}

Elsewhere in code, you would check to see if the widget is active by:

public boolean isWidgetActive(Context context){
    Context appContext = context.getApplicationContext();
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    return prefs.getBoolean(Constants.WIDGET_ACTIVE, false);
}

Widget for lock screen

Since Android 4.2, it is possible to add home screen app widgets to the lock screen of an Android device. To enable your widget for the look screen you need to add keyguard category in the android:widgetCategory attribute in the AppWidgetProviderInfo XML file. The following code shows an example.

<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:widgetCategory="keyguard|home_screen"
    ...>
...
</appwidget-provider>

Take note of the widgetCategory attribute. This specifies if your widget can be available on the lock screen as well as on the home screen. All widgets are available on the home screen by default, and if not specified. Android 4.2 included the keyguard option, indicating that the widget can be added to the lock screen.

If your widget is displayed on a lock screen, you might want to show different data, or a different layout. To detect if the widget is on a lock screen, you request the widget options using AppWidgetManager’s getWidgetOptions() method. This method returns a bundle, which can be queried for the AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY int. This will either be a WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD.

The sample code below checks for the AppWidgetHost, and displays a different layout for each host type.

AppWidgetManager appWidgetManager;
int widgetId;
Bundle myOptions = appWidgetManager.getAppWidgetOptions(widgetId);

// get the value of OPTION_APPWIDGET_HOST_CATEGORY
int category = myOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1);

// if the value is WIDGET_CATEGORY_KEYGUARD, it's a lockscreen widget
boolean isKeyguard = category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;

int baseLayout = isKeyguard ? R.layout.keyguard_widget_layout : R.layout.widget_layout;

Load images via Glide

We can use Glide to load image to ImageView and change it size. This is useful solution if your are exceed this constraint: The total Bitmap memory used by the RemoteViews object cannot exceed that required to fill the screen 1.5 times, ie. (screen width x screen height x 4 x 1.5) bytes.

public void onReceive(Context context, Intent intent) {
    super.onReceive(context, intent);

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    ComponentName thisWidget = new ComponentName(context, HomescreenWidgetProvider.class);

    for (int id : appWidgetManager.getAppWidgetIds(thisWidget)) {
        final RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);

        AppWidgetTarget appWidgetTarget = new AppWidgetTarget(context, R.id.ivImage, remoteViews, appWidgetManager.getAppWidgetIds(thisWidget)) {
            @Override
            public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {
                super.onResourceReady(resource, transition);
            }
        };

        GlideApp.with(context.getApplicationContext()).asBitmap().override(612, 124)
                .load(uri)
                .placeholder(R.mipmap.ic_launcher)
                .into(appWidgetTarget);
    }
};

Useful links

comments powered by Disqus