Adding Android Widgets Without User Interaction

As part of an ongoing project, I’m building a simplified replacement for Trebuchet (Android’s default launcher). One aspect that I need to simplify is showing Widgets on the launcher without requiring the user to add them by hand. Normally, Android restricts this by requiring apps to use the AppWidgetManager.EXTRA_APPWIDGET_ID  intent to launch a system picker. The only way around this is for system apps to request android.permission.BIND_APPWIDGET in their manifest, so this method is only available on rooted devices where the app is installed at the system level (in /system/app  or /system/priv-app).

Adding Widgets

To display and manage widgets, two objects are required; AppWidgetManager and AppWidgetHost . These can be instantiated by calling the following in an appropriate place (an activity’s onCreate()  seems fairly good). APPWIDGET_HOST_ID  is a int that’s unique to your app, Android doesn’t provide any guidelines on picking one.

mAppWidgetManager = AppWidgetManager.getInstance(this);
mAppWidgetHost = new AppWidgetHost(this, APPWIDGET_HOST_ID);
mAppWidgetHost.startListening();

That last line is important. It allows widgets to update themselves with new information. Otherwise that clock widget is going to be stuck at whatever time the activity was created.

From here, we need to get an appWidgetId. This is just an int , but it’s used to uniquely identify an instance of a widget. That’s easy enough:

int appWidgetId = mAppWidgetHost.allocateAppWidgetId();

Here’s the tricky bit. We need to bind the appWidgetId  to a specific widget. In this example I’m always using the same widget, uk.co.jacobmansfield.android.widgetProvider.dummyWidget.

mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, new ComponentName("uk.co.jacobmansfield.android.widgetProvider", "uk.co.jacobmansfield.android.widgetProvider.dummyWidget"));

Something to note: If this fails, the widget will still be bound, but you’ll have a default widget that contains the text “Failed to add widget”. I think this is meant to be more user friendly that Trebuchet or another launcher crashing, but it does make error handling a bit trickier.

Finally, we need to add the widget to the view. This works the same as adding any other view element, though we do need to put the widget into an AppWidgetHostView  first:

AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
AppWidgetHostView hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
hostView.setAppWidget(appWidgetId, appWidgetInfo);
((LinearLayout) findViewById(R.id.room_details_text)).addView(hostView);

There’s a few other bits that are fairly self-explanatory, but leave a comment or poke me on Twitter and I’ll write those up as well.

Full code for HomeScreen.java

package uk.co.jacobmansfield.android.launcher;

import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v17.leanback.app.BackgroundManager;
import android.support.v4.app.FragmentActivity;
import android.util.DisplayMetrics;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

public class HomeScreen extends FragmentActivity {
    private AppWidgetManager mAppWidgetManager;
    private AppWidgetHost mAppWidgetHost;
    static final int APPWIDGET_HOST_ID = 4242;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.homescreen);

        mAppWidgetManager = AppWidgetManager.getInstance(this);
        mAppWidgetHost = new AppWidgetHost(this, APPWIDGET_HOST_ID);
        mAppWidgetHost.startListening();

        int appWidgetId = mAppWidgetHost.allocateAppWidgetId();

        mAppWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, new ComponentName("uk.co.jacobmansfield.android.widgetProvider", "uk.co.jacobmansfield.android.widgetProvider.dummyWidget"));

        AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
        AppWidgetHostView hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
        hostView.setAppWidget(appWidgetId, appWidgetInfo);
        ((LinearLayout) findViewById(R.id.room_details_text)).addView(hostView);
    }

    @Override
    protected void onStop() {
        super.onStop();
        mAppWidgetHost.stopListening();
    }

    public void removeWidget(AppWidgetHostView hostView) {
        mAppWidgetHost.deleteAppWidgetId(hostView.getAppWidgetId());
        ((LinearLayout) findViewById(R.id.room_details_text)).removeView(hostView);
    }
}

 

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.