Whilst I appreciate that an App Widget does not support a WebView
directly, is it at all possible to use an ImageView
(which is supported), and a technique like described here to generate the image for the ImageView
? The WebView
wouldn't be used directly, but only used in the background in order to provide the image for the ImageView
.

- 1
- 1

- 11,491
- 21
- 85
- 181
-
Whether that webView will change at different times? – Nigam Patro Nov 28 '15 at 13:11
-
1Yes, the App Widget would be updated at regular intervals, and each time the image for the App Widget (retrieved using the `WebView`) would be refreshed accordingly. So I'm thinking that the `WebView` would be run as or within a background Service. I've tried this suggestion here (http://stackoverflow.com/a/18989695/4070848) but I can't get it to work as I want. – drmrbrewer Nov 28 '15 at 15:00
-
But, how the webView will be updated without any interaction? – Nigam Patro Nov 28 '15 at 15:22
-
If a background `Service` is running, can't you interact with the `Service` at intervals in order to update a `WebView` and fetch the generated image? – drmrbrewer Nov 28 '15 at 15:24
-
OK.But we cant take webview inside service. Sorry for that. – Nigam Patro Nov 28 '15 at 15:25
-
So the post linked in my comment above is wrong? It suggests that a webview in a service is possible. – drmrbrewer Nov 28 '15 at 18:09
-
Whether you tried with that code, bcoz I tried and checked, its not wokring. – Nigam Patro Nov 28 '15 at 20:36
3 Answers
This is possible, apparently, at least on my device, though your mileage may vary.
In the manifest, we register the Widget's <receiver>
as normal, and also register our Service
that handles the WebView
and its image capture.
<manifest ... >
...
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application ... >
...
<receiver
android:name=".WebWidgetProvider"
android:label="@string/widget_name" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_info" />
</receiver>
<service android:name=".WebShotService" />
</application>
</manifest>
In the Widget's info file, widget_info.xml
, I've set the minimum size to 4 x 2.
<?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="292dip"
android:minHeight="146dip" />
For this example, the Widget's layout, widget_layout.xml
, is simply an ImageView.
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@drawable/ic_launcher" />
The AppWidgetProvider
in this example is overriding only the onUpdate()
method, as we're using the same ACTION_APPWIDGET_UPDATE
to trigger our own updates with AlarmManager
. Please note that the example alarm will fire every 30 seconds, as long as there's an active Widget. You probably don't want to do this outside of testing, as Widget updates can be rather costly.
public class WebWidgetProvider extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// We can't trust the appWidgetIds param here, as we're using
// ACTION_APPWIDGET_UPDATE to trigger our own updates, and
// Widgets might've been removed/added since the alarm was last set.
final int[] currentIds = appWidgetManager.getAppWidgetIds(
new ComponentName(context, WebWidgetProvider.class));
if (currentIds.length < 1) {
return;
}
// We attach the current Widget IDs to the alarm Intent to ensure its
// broadcast is correctly routed to onUpdate() when our AppWidgetProvider
// next receives it.
Intent iWidget = new Intent(context, WebWidgetProvider.class)
.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, currentIds);
PendingIntent pi = PendingIntent.getBroadcast(context, 0, iWidget, 0);
((AlarmManager) context.getSystemService(Context.ALARM_SERVICE))
.setExact(AlarmManager.RTC, System.currentTimeMillis() + 30000, pi);
Intent iService = new Intent(context, WebShotService.class);
context.startService(iService);
}
}
In the Service
, we instantiate a WebView
, and add it to a FrameLayout
, which in turn gets added to the WindowManager
with zero for its width and height parameters. We then load a test URL in the WebView
, and when the page finishes, we force the WebView
to layout to an appropriate size, and draw it to our target Bitmap
via a Canvas
object. This is done after a slight delay to allow the WebView
to fully render itself, lest we end up with a blank, white image. The Bitmap
is then set on a RemoteViews
object to update the ImageView
in the Widget's layout. I've left a Toast
in the code for testing purposes, as our example Widget may update more frequently than Stack Overflow's front page refreshes.
public class WebShotService extends Service {
private WebView webView;
private WindowManager winManager;
public int onStartCommand(Intent intent, int flags, int startId) {
winManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
webView = new WebView(this);
webView.setVerticalScrollBarEnabled(false);
webView.setWebViewClient(client);
final WindowManager.LayoutParams params =
new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT);
params.x = 0;
params.y = 0;
params.width = 0;
params.height = 0;
final FrameLayout frame = new FrameLayout(this);
frame.addView(webView);
winManager.addView(frame, params);
webView.loadUrl("http://stackoverflow.com");
return START_STICKY;
}
private final WebViewClient client = new WebViewClient() {
public void onPageFinished(WebView view, String url) {
final Point p = new Point();
winManager.getDefaultDisplay().getSize(p);
webView.measure(MeasureSpec.makeMeasureSpec((p.x < p.y ? p.y : p.x),
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec((p.x < p.y ? p.x : p.y),
MeasureSpec.EXACTLY));
webView.layout(0, 0, webView.getMeasuredWidth(), webView.getMeasuredHeight());
webView.postDelayed(capture, 1000);
}
};
private final Runnable capture = new Runnable() {
@Override
public void run() {
try {
final Bitmap bmp = Bitmap.createBitmap(webView.getWidth(),
webView.getHeight(), Bitmap.Config.ARGB_8888);
final Canvas c = new Canvas(bmp);
webView.draw(c);
updateWidgets(bmp);
}
catch (IllegalArgumentException e) {
e.printStackTrace();
}
stopSelf();
}
};
private void updateWidgets(Bitmap bmp) {
final AppWidgetManager widgetManager = AppWidgetManager.getInstance(this);
final int[] ids = widgetManager.getAppWidgetIds(
new ComponentName(this, WebWidgetProvider.class));
if (ids.length < 1) {
return;
}
final RemoteViews views = new RemoteViews(getPackageName(), R.layout.widget_layout);
views.setImageViewBitmap(R.id.widget_image, bmp);
widgetManager.updateAppWidget(ids, views);
Toast.makeText(this, "WebWidget Update", 0).show();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
Here's a screenshot of the Widgety goodness.

- 38,532
- 8
- 99
- 95
-
This is genius! I actually had much of the framework in place already, in terms of using `AlarmManager` to refresh my widgets, so with a few additions based on your post I've managed to get SO in a widget! ... now to get what I *actually want* in the widget instead! Thanks so much. Genius. – drmrbrewer Nov 29 '15 at 21:59
-
Not too genius, actually. I just realized there's absolutely no reason to broadcast back to the Receiver. We can update the Widget right from the Service. Don't know what I was thinking, but, in my defense, it was pretty late last night when I threw this together. I'll update my answer with a saner method, when I get a minute to polish it. Sorry about that. Cheers! – Mike M. Nov 30 '15 at 01:43
-
2I've updated my answer to remove the unnecessary broadcast and file write. I also got rid of a few other useless things, and tweaked some stuff here and there. Hope this one works for ya, too. Cheers! – Mike M. Nov 30 '15 at 03:57
-
I'll give that a go too. I can see the benefit of removing the broadcast (update the widget from the `Service`), particularly in terms of avoiding a file write. But, to me it seems quite intuitive to do all the widget updating stuff in the same place, in the `AppWidgetProvider`, so the broadcast/receive communication seemed to be quite neat (`Service` broadcasts that it's done, and `AppWidgetProvider` hears that and grabs the result). Is there any way to communicate the bitmap back to the `AppWidgetProvider` other than by a file write / read? – drmrbrewer Nov 30 '15 at 09:54
-
Actually, I was writing a comment to say that I think I was pushing it by loading an image from storage in a Receiver, when I realized that it was unnecessary. BroadcastReceivers aren't meant to live very long, and though I think the decode call will block effectively, it's usually best to make the `onReceive()` method as short and quick as possible. I agree with you, though, that having all of the actual Widget stuff in one place is more organized and consistent, and that's probably what was in my head while I was doing this initially. – Mike M. Nov 30 '15 at 10:15
-
It is possible to pass a Bitmap as an extra on the Intent, but there is a practical limit on the amount of data one can carry; usually about 1MB. Of course, you can control the size of the captured Bitmap, but on large displays, this restriction might give undesirable results. Also, just as an FYI, you can still access my original answer in the revision history, if you need. – Mike M. Nov 30 '15 at 10:15
-
OK, thanks for the further insight. I'll look into the available options, but may in the end just put up with the delegation of some of the functionality of the `AppWidgetProvider` to the `Service` -- that in itself makes some sort of sense. I'll perhaps try to keep as much as possible in the former, passing various parameters (e.g. determining widget size and layout) via extras on the `Intent`, so that the `Service` is limited to creating and loading the bitmap into the layout. Thanks again. – drmrbrewer Nov 30 '15 at 11:28
-
@MikeM. I got "permission denied for this window type" error on `winManager.addView(frame, params);` on `Android 6.0 API 23`. How I can solve that? – hasanghaforian Jan 29 '17 at 19:23
-
@MikeM. is there any way to avoid the need for the `SYSTEM_ALERT_WINDOW` permission? e.g. using a tweak as per @Vikram's answer? Some users are understandably nervous about granting this permission... and for Android 6+ you have to get this permission at runtime. – drmrbrewer Jun 26 '17 at 11:10
-
@drmrbrewer Hmm, maybe? I don't recall why it's specifically a `TYPE_SYSTEM_OVERLAY`, or if it has to be. I just remember `WebView` won't load anything unless it's attached to `WindowManager`, which is why we had to do it like this originally. Vikram's answer seems to indicate it'll work as is if you just change the type, but I'd have to test that before I could say for certain. I'll try it out when I get some time later, and update the answer, if so. Apparently you're not the only one with that concern. Not sure how I missed hasanghaforian's comment up there. – Mike M. Jun 26 '17 at 17:17
-
Thanks MikeM. It's a shame that at least a static form of WebView isn't supported in an AppWidget out of the box, but your solution still works well... would be even better without the overlay permission requirement! – drmrbrewer Jun 26 '17 at 18:34
-
1@drmrbrewer Well, `TYPE_TOAST` seems to work without permission, at least on the few versions I was able to test on. `TYPE_APPLICATION_PANEL` crashes on earlier versions, which doesn't surprise me, since it's being added from a `Service`. Not sure if that restriction was changed in later versions. Do note that `TYPE_TOAST` is now deprecated in Android O, to be substituted by `TYPE_APPLICATION_OVERLAY`, but I'm not able to play around with that yet, so I'm uncertain as to what more may be required, or the exact behavior to be expected. I'll wait to edit the answer until I get better information – Mike M. Jun 27 '17 at 02:16
-
@drmrbrewer You might ultimately need to choose the appropriate type in code depending on the API level. – Mike M. Jun 27 '17 at 02:17
-
@MikeM. and it seems that `TYPE_APPLICATION_OVERLAY` also requires the `SYSTEM_ALERT_WINDOW` permission: https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#TYPE_APPLICATION_OVERLAY. So ultimately, as users migrate to Android O and above, we'll be left with no choice but to request this permission at runtime for this WebView method to work? – drmrbrewer Jun 27 '17 at 07:27
-
1@drmrbrewer Yeah, that seems to be the case, though I'd imagine not a lot of users are going to have O for a while. Also, I'm not sure how exactly they're handling that deprecation. It could be that `TYPE_TOAST` is still usable, and they'll gradually phase it out completely, but considering some of the other changes they've made in O, I really wouldn't be shocked if they straight up disabled it already. As mentioned, I can't test anything O atm, and I've not really been keeping up on all the API changes announced. If I think of any other way to do this, I'll let ya know. – Mike M. Jun 27 '17 at 07:40
-
@MikeM. it looks like we're stuck between a rock and a hard place. I've been trying `TYPE_TOAST` to avoid the permission, but I'm having crash reports from users on Android 7.1. It seems that from 7.1 they made `TYPE_TOAST` fade away after a short time (like a toast)... see https://github.com/commonsguy/cwac-presentation/issues/9 and https://issuetracker.google.com/issues/37129633 -- probably leading to crashes when used in this webview widget method... I wonder if there is any way of prolonging the duration of the `TYPE_TOAST` so that it lasts long enough for the `WebView` to render? – drmrbrewer Jul 02 '17 at 09:51
-
1@drmrbrewer Hmm, dunno. From those links, that does seem the likely culprit, but more details from the crashes would be helpful in determining that as the specific issue. Looking through the source, I don't see any way to change the duration, really. I'll mull it over, and, as mentioned, I'll try to think of some other way altogether to do this. I've been away from home for a while, and I'm just getting settled back in to my development stuff, but I'll keep you updated if I get anything working. – Mike M. Aug 05 '17 at 08:38
-
I've just stumbled across Chrome headless (https://developers.google.com/web/updates/2017/04/headless-chrome)... @MikeM. I wonder whether this is an alternative way of rendering web content for use in an App Widget, without the need to use an invisible WebView, and without the need for the "draw over other apps" permission? – drmrbrewer Dec 05 '17 at 10:50
-
1Scrub that idea... I've just had it confirmed that the headless capability of Chrome is not supported in Chrome for Android :( – drmrbrewer Dec 05 '17 at 18:56
-
@MikeM. great solution! Would this work if the `WebView` was inflated from a layout resource? I'm trying and not succeeding. I'm attaching the `WebViewClient` and doing the update once the `onPageLoad` method is invoked. But the widget is not updated... I'm also not doing all this in a service, since that proved to be a problem in recent Android versions, all is done in the `AppWidgetProvider`. I'm not loading a web-page, but a simple local text resource, so everything should be done fast enough. – Eir Apr 26 '20 at 09:58
-
1@Eir I can't think of any reason an inflated instance would perform differently, off the top of my head. However, if the only thing in the layout XML is the `
`, I don't think you really gain anything by inflating instead. On the other hand, if there are other `View`s in there, the measure, layout, and draw would have to be adjusted a bit; otherwise, I can see how it might result in a blank or empty result. I can't really be any more specific than those broad scenarios, other than to recommend that you try to step through a capture run in your debugger. – Mike M. Apr 26 '20 at 10:15 -
@MikeM. of course there are other views in there :) Without the `WebView` they are all rendered fine as an image. When I add the `WebView` element, it stays blank, since it needs some time to load the contents. When I try to update the widget once I get the feedback from the `WebView` (via `WebViewClient.onPageLoad`), the widget does not update at all. The debugger shows me that the widget update code is run, no error is produced, but still, the widget is not updated. Can't figure out if that code gets triggered in a different way, and thus is ignored by the `AppWidgetManager`... – Eir Apr 26 '20 at 11:04
-
1@Eir Dunno. This is kind of an old solution, and I haven't really done anything similar recently, so I can't think of any relevant issues, off-hand. I don't know that I would even attempt or recommend this, these days. If you have a link to your project somewhere publicly accessible, I can take a quick look, but no guarantees. Apart from that, though, we can't really do remote debugging in comments here. – Mike M. Apr 26 '20 at 11:12
-
@MikeM. actually, your comments were very helpful for me, thanks! I'd like to ask you one more question: why is there a need to leave a second for the `WebView` to render itself properly? Isn't there a more precise method, i.e. a call back when the view has finished rendering? Also, would you achieve the same if you use `Thread.sleep` for that pause? – Eir Apr 26 '20 at 13:35
-
@Eir IIRC, `WebView` does its rendering on worker threads, and there isn't any kind of callback or queue that we could take advantage of to know when that's actually done. I'm not 100% certain that that's (still) the case, since it's been so long, but if I'd've known of some such mechanism then, I certainly would've gone that way, rather than use that arbitrary delay there. I know that's really hacky. – Mike M. Apr 26 '20 at 13:44
Adding to the above answer,
For people who get this error - Unable to add window. Permission denied for this window type at least in my case with devices running marshmallow the type need to be changed.
Simply replace
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY to either
WindowManager.LayoutParams.TYPE_TOAST (or)
WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
and it will work perfectly fine.
Note: Checked and confirmed with Nexus6p running the latest version of marsmallow.
The WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY requires the permission android.permission.INTERNAL_SYSTEM_WINDOW which can be added only for android system related apps.
Also don't forget to change the web view height and width once the page has finished loading at onPageFinished() for people who are looking to parse data using Javascript.
I completely overlooked the importance of this in the above answer and ended up wasting a lot of time.
Initially my assumption was since my requirement was not to display the contents in the web view to the widget via an Image view this was not required.
As it turns out javascript cannot parse the data from the web view unless the view width and height is not zero.

- 995
- 1
- 9
- 15
-
Does this mean that the `SYSTEM_ALERT_WINDOW` permission is no longer required? i.e. if you're using `WindowManager.LayoutParams.TYPE_TOAST` instead of `LayoutParams.TYPE_SYSTEM_OVERLAY`? It's slightly problematic having to request this permission from users... it's not enabled by default and some users are understandably nervous about doing so. – drmrbrewer Jun 26 '17 at 10:08
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY only work after Android M. And need configure permission Settings.canDrawOverlays(context)

- 11
- 2