Tutorial – How to write an Android App Widget to control your television
by Riley MacDonald, October 14, 2017

Most Android phones come stock with Infrared Light Emitting Diodes. My service provider also baked the Peel remote application into the Android OS. This remote works great… but nearly every initial button press invokes a shameless full screen advertisement (often with video and sound). Very annoying! Seems like a simple application I could just write for myself.

Here’s an overview of how we’ll implement this:

  • Add the Android Manifest items.
  • Add the required metadata and layout.
  • Create a subclass of AppWidgetProvider.
  • Create an IRCommand and ConsumerIrManager helper/wrapper.

Android has already provided a tutorial for implementing widgets. If you’re familiar with widgets and just interested in the IR stuff, you can skip to the end of this post.

Add the manifest items
The ConsumerIrManager requires the TRANSMIT_IR permission. Add it to your manifest

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

To make the widget available to the Android system add the following:

1
2
3
4
5
6
7
<receiver android:name=".widget.MyAppWidgetProvider" >
  <intent-filter>
    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
  </intent-filter>
  <meta-data android:name="android.appwidget.provider"
             android:resource="@xml/app_widget_provider" />
</receiver>

Don’t worry about MyAppWidgetProvider and @xml/app_widget_provider we’ll create them later.

Add the metadata and layouts
We’ll start by adding the required meta-data from above. Create a new xml file in res/xml. It should look something like this:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="250dp"
    android:minHeight="40dp"
    android:updatePeriodMillis="0"
    android:initialLayout="@layout/television_remote_appwidget_layout"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen">
</appwidget-provider>

Note the minWidth and minHeight use set values for the widget size (1 x 1 -> 4 x 4 etc.). See the documentation for details.

Now we can add the layout the widget will use. Widgets don’t support all types of views. Refer to the official documentation if you’re experiencing trouble. I just used a LinearLayout and some ImageViews. I called my layout television_remote_appwidget_layout and added it to the res/layout directory. Note I also gave my images views ids to help identify them later. I’ve omitted the other ImageViews for simplicity. They’re mentioned below in an enum.

1
2
3
4
5
6
7
8
9
10
11
12
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <ImageView
        android:id="@+id/widget_tv_power"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/ic_action_power"
        android:contentDescription="@string/power" />
</LinearLayout>

Android Studio provides lots of clip art I’ve found useful for applications like this. Right click the drawable directory and choose “new Vector Asset”. Click the “clip art” button and choose an asset. Give it a name and hit add. I was able to find icons for power, mute, volume up/down and source to use in my widget. Once your application is deployed to the device you should be able to add the widget to your homescreen by long clicking and choosing widgets.

Create a subclass of AppWidgetProvider
We defined receiver android:name=".widget.MyAppWidgetProvider" in the Android Manifest. We’ll create this now. Create a new class called MyAppWidgetProvider with a superclass of AppWidgetProvider. Override the onUpdate() and onReceive() methods. Your methods should look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    for (int appWidgetId : appWidgetIds) {
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.television_remote_appwidget_layout);
        // currently doesn't respond to any clicks -- we'll cover this momentarily
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }
 
    super.onUpdate(context, appWidgetManager, appWidgetIds);
}
 
@Override
public void onReceive(Context context, Intent intent) {
    super.onReceive(context, intent);
}

Transmitting IR Commands
The widget is all ready to go but it does nothing. We’ll come back and connect the ImageView to respond to a click after this step.

I borrowed this object from http://stackoverflow.com/users/1679571/randy and created an abstract class using it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public abstract class IRCommand {
    private int frequency;
    private int[] pattern;
 
    IRCommand(final String irData) {
        List<String> list = new ArrayList<>(Arrays.asList(irData.split(" ")));
        list.remove(0);
        int frequency = Integer.parseInt(list.remove(0), 16); // frequency
        list.remove(0);
        list.remove(0);
 
        frequency = (int) (1000000 / (frequency * 0.241246));
        int pulses = 1000000 / frequency;
        int count;
 
        int[] pattern = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            count = Integer.parseInt(list.get(i), 16);
            pattern[i] = count * pulses;
        }
 
        this.frequency = frequency;
        this.pattern = pattern;
    }
 
    int getFrequency() { return frequency; }
    int[] getPattern() { return pattern; }
}

Let’s create an implementation of IRCommand specific to the television model. I found all the codes I needed on Remote Central. I saved them as Strings describing their behavior. I kept the constructor private and create some static creation methods for now. This should probably be refactored as a singleton which caches the constructed commands… but I’m leaving it as is for this example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SamsungTvIRCommand extends IRCommand {
    private final static String TV_POWER_HEX =          "0000 006d 0022 0003 00a9 00a8 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0040 0015 0015 0015 003f 0015 003f 0015 003f 0015 003f 0015 003f 0015 003f 0015 0702 00a9 00a8 0015 0015 0015 0e6e";
    private final static String TV_MUTE_HEX =           "0000 006d 0000 0022 00a9 00a8 0015 003f 0015 0015 0015 003f 0015 0016 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 0015 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 0015 0015 003f 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 003f 0015 075f";
    private static final String TV_VOLUME_DOWN_HEX =    "0000 006d 0022 0003 00a9 00a8 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 003f 0015 0015 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 0015 0015 003f 0015 003f 0015 003f 0015 003f 0015 0702 00a9 00a8 0015 0015 0015 0e6e";
    private final static String TV_VOLUME_UP_HEX =      "0000 006d 0022 0003 00a9 00a8 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 003f 0015 003f 0015 003f 0015 003f 0015 0702 00a9 00a8 0015 0015 0015 0e6e";
    private final static String TV_SOURCE_HEX =         "0000 006C 0000 0022 00AD 00AD 0016 0041 0016 0041 0016 0041 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0041 0016 0041 0016 0041 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0041 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0041 0016 0041 0016 0041 0016 0041 0016 0041 0016 0041 0016 0041 0016 06FB";
 
    public static final SamsungTvIRCommand TV_POWER = new SamsungTvIRCommand(TV_POWER_HEX);
    public static final SamsungTvIRCommand TV_MUTE = new SamsungTvIRCommand(TV_MUTE_HEX);
    public static final SamsungTvIRCommand TV_VOLUME_DOWN = new SamsungTvIRCommand(TV_VOLUME_DOWN_HEX);
    public static final SamsungTvIRCommand TV_VOLUME_UP = new SamsungTvIRCommand(TV_VOLUME_UP_HEX);
    public static final SamsungTvIRCommand TV_SOURCE = new SamsungTvIRCommand(TV_SOURCE_HEX);
 
    private SamsungTvIRCommand(String irData) { super(irData); }
}

This objects gives us everything we need to start transmitting IR commands using the ConsumerIrManager. Let’s create a helper that works better with our IRCommand object.

1
2
3
4
5
6
7
8
9
10
11
public class ConsumerIrManagerHelper {
    private ConsumerIrManager consumerIrManager;
 
    public ConsumerIrManagerHelper(ConsumerIrManager consumerIrManager) {
        this.consumerIrManager = consumerIrManager;
    }
 
    public void transmitIRCommand(IRCommand irCommand) {
        consumerIrManager.transmit(irCommand.getFrequency(), irCommand.getPattern());
    }
}

Now we can just pass our implementation of IRCommand to transmitIRCommand and it will handle the rest. Let’s finish by hooking up the widget button.

Back to MyAppWidgetProvider
I was blocked by this detail for awhile. The Android documentation instructs you to create a PendingIntent by invoking getActivity() which opens an activity. I wanted to process the command without opening any activity. You can accomplish this by calling getBroadcast() instead. This will result in the onReceive() method being invoked on any button click. I added the following enum and methods to MyAppWidgetProvider (for now).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public enum SamsungTVActions {
    POWER(R.id.widget_tv_power, "tv_power_action", TV_POWER);
// Add additional actions as needed
//    MUTE(R.id.widget_tv_mute, "tv_mute_action", TV_MUTE),
//    VOL_DOWN(R.id.widget_tv_volume_down, "tv_volume_down", TV_VOLUME_DOWN),
//    VOL_UP(R.id.widget_tv_volume_up, "tv_volume_up", TV_VOLUME_UP),
//    SOURCE(R.id.widget_tv_source, "tv_source_action", TV_SOURCE);
 
    private int viewId;
    private String action;
    private IRCommand commandHex;
 
    SamsungTVActions(int viewId, String action, IRCommand commandHex) {
        this.viewId = viewId;
        this.action = action;
        this.commandHex = commandHex;
    }
}
 
private void setupViewClickPendingIntents(Context context, RemoteViews views) {
    for (SamsungTVActions samsungTvActions : SamsungTVActions.values())
        views.setOnClickPendingIntent(samsungTvActions.viewId,
                createTVActionIntent(context, samsungTvActions.action));
}
 
private PendingIntent createTVActionIntent(Context context, String action) {
    Intent intent = new Intent(context, getClass());
    intent.setAction(action);
    return PendingIntent.getBroadcast(context, 0, intent, 0);
}

I used a combination of the enum and methods to reduce duplicated code. I’m not sure if creating multiple Intent objects is necessary. Please leave a comment if you know.

Now you can just add the call to setupViewClickPendingIntents() to the existing onUpdate() so it looks something like this:

1
2
3
4
5
6
7
8
9
10
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    for (int appWidgetId : appWidgetIds) {
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.television_remote_appwidget_layout);
        setupViewClickPendingIntents(context, views);
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }
 
    super.onUpdate(context, appWidgetManager, appWidgetIds);
}

Summary
Success, the television can now be controlled by a handy little widget on my homescreen (for all those times the pesky remote is out of reach). I took some more time to customize the layout to make it more aesthetically appealing.

I’m not happy with the SamsungTVActions enum. It’s not abstract enough. I would have put the enum in the abstract class but Java restricts you from abstracting an enum. Ideally the MyAppWidgetProvider would program to an interface instead of an implementation. Down the road it will take more refactoring to add support for multiple televisions.

Open the comment form

Leave a comment:

Comments will be reviewed before they are posted.

User Comments:

Josh on 2020-04-24 14:10:25 said:
Is the full source code available?

Riley MacDonald on 2020-04-01 19:06:13 said:
I used remote central to find the codes (http://www.remotecentral.com/cgi-bin/codes/samsung/tv_functions/)

dave on 2020-03-21 15:01:22 said:
Hi, I know this is an old post but it's the most simple example (and working) I ever found. Just a question, where do you find the codes? I found the pronto hex codes at irdb.tk/codes/ but they doesn't match with the values used by you thanks again for your work