How to get app widget size? - android

I have an Android app widget and I would like to extend it to support several sizes and also re-sizable with Honeycomb.
The problem is that I need to know what is the size of the widget , so I can know how much content I can put in it.
How do I read the app widget size?
I couldn't find anything.

I know it's old question, but there's a newer answer to it (that I believe was not available on the time, as it is API 16 and up only):
you can call:
Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
that will give the same options that is passed to your receiver during
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions)
with this Bundle options you can query for the sizing using the constants:
AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT
AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH
AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT
AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH
Remembering that Samsung likes to be different, so you probably need a special Handler for TouchWiz (code bellow copied directly from my app):
#Override
public void onReceive(Context context, Intent intent) {
if (intent == null || intent.getAction() == null)
return;
// I FUCKING HATE SAMSUNG!
if (intent.getAction().contentEquals("com.sec.android.widgetapp.APPWIDGET_RESIZE") &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
handleTouchWiz(context, intent);
}
super.onReceive(context, intent);
}
#TargetApi(16)
private void handleTouchWiz(Context context, Intent intent) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int appWidgetId = intent.getIntExtra("widgetId", 0);
int widgetSpanX = intent.getIntExtra("widgetspanx", 0);
int widgetSpanY = intent.getIntExtra("widgetspany", 0);
if (appWidgetId > 0 && widgetSpanX > 0 && widgetSpanY > 0) {
Bundle newOptions = new Bundle();
// We have to convert these numbers for future use
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, widgetSpanY * 74);
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, widgetSpanX * 74);
onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
}
}

Related

How to catch widget size changes on devices where onAppWidgetOptionsChanged not getting called?

Devices like Samsung Galaxy S3 working with Android version 4.1.2 has a bug which prevents onAppWidgetOptionsChanged to be called.
So, how can we get information related to changed sizes?
I am adding to Frankish's answer with how I handle receiving this broadcast:
#Override
public void onReceive(Context context, Intent intent) {
// Handle TouchWiz
if(intent.getAction().contentEquals("com.sec.android.widgetapp.APPWIDGET_RESIZE")) {
handleTouchWiz(context, intent);
}
super.onReceive(context, intent);
}
private void handleTouchWiz(Context context, Intent intent) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int appWidgetId = intent.getIntExtra("widgetId", 0);
int widgetSpanX = intent.getIntExtra("widgetspanx", 0);
int widgetSpanY = intent.getIntExtra("widgetspany", 0);
if(appWidgetId > 0 && widgetSpanX > 0 && widgetSpanY > 0) {
Bundle newOptions = new Bundle();
// We have to convert these numbers for future use
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, widgetSpanY * 74);
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, widgetSpanX * 74);
onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
}
}
You may need to change how you handle that bundle if you are using MAX_HEIGHT or MAX_WIDTH which I am not.
I have discovered that you can catch RESIZE action in onReceive function.
if(intent.getAction().contentEquals("com.sec.android.widgetapp.APPWIDGET_RESIZE"))
You'll get the following values from this intent's getExtras() bundle:
int appWidgetId = bundle.getInt("widgetId"); // bundle.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
int widgetSpanX = bundle.getInt("widgetspanx", 4);
int widgetSpanY = bundle.getInt("widgetspany", 1);
Now you can use these values to update the view or store them in a static integer map variable to use in the next onUpdate.

Check if a widget is exists on homescreen using appWidgetId

I am using AlarmManager to update my widgets. And I want to stop it if there is no widget on homescreen. But I am facing a problem with detecting if there is no widget on home screen.
As whenever I try to get the AppWidgetIds using this way:
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int[] appWidgetIDs = appWidgetManager
.getAppWidgetIds(new ComponentName(context, Widget.class));
I get the a length of appWidgetIDs while actually there is no widget on homescreen. Why?
Therefore, I would like to know if there is a way to detect that a widget id is exists on homescreen.
Thank you upfront.
Congratulations, you've encountered phantom appwidgets. It appears to be documented on the Android issue tracker. They usually occur when the configuration activity for an appwidget is canceled, though it seems to be through improper implementation of the configuration activity; developers neglect to include the appwidget ID as an extra when setting the activity result to RESULT_CANCELED. (even Google's ApiDemos sample application neglects to do this!)
The proper implementation is like this:
public class AppWidgetConfigActivity extends Activity {
private int appWidgetId;
private Intent resultValue;
protected void onCreate(bundle saved) {
super.onCreate(saved);
// get the appwidget id from the intent
Intent intent = getIntent();
appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
// make the result intent and set the result to canceled
resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
setResult(RESULT_CANCELED, resultValue);
// if we weren't started properly, finish here
if (appwidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish();
}
/* ... */
}
/* ... */
private void finishConfigure() {
/* finish configuring appwidget ... */
setResult(RESULT_OK, resultValue);
}
}
Thus far I know of no way to detect the presence of a phantom appwidget without doing your own bookkeeping. I suggest storing a SharedPreferences value indicating that the configuration activity was not canceled and then querying this value in your other code. You can also use this information to "delete" a phantom widget if you come across one. In your appwidget configuration activity:
private void finishConfigure() {
/* finish configuring appwidget ... */
setResult(RESULT_OK, resultValue);
String key = String.format("appwidget%d_configured", appwidgetId);
SharedPreferences prefs = getSharedPreferences("widget_prefs", 0);
prefs.edit().putBoolean(key, true).commit;
}
Then you can check that you have at least one non-phantom appwidget like so:
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
AppWidgetHost appWidgetHost = new AppWidgetHost(context, 1); // for removing phantoms
SharedPreferences prefs = getSharedPreferences("widget_prefs", 0);
boolean hasWidget = false;
int[] appWidgetIDs = appWidgetManager.getAppWidgetIds(new ComponentName(context, Widget.class));
for (int i = 0; i < appWidgetIDs.length; i++) {
int id = appWidgetIDs[i];
String key = String.format("appwidget%d_configured", id);
if (prefs.getBoolean(key, false)) {
hasWidget = true;
} else {
// delete the phantom appwidget
appWidgetHost.deleteAppWidgetId(id);
}
}
if (hasWidget) {
// proceed
} else {
// turn off alarms
}

Check Widget is Placed on Android Screen

Can someone tell me how to check that my widget have been placed on the homescreen?
I have some code in my app that should run only if the widget is placed on the homescreen.
Just saying, but...
int ids[] = AppWidgetManager.getInstance(this).getAppWidgetIds(new ComponentName(this,MyAppWidgetProvider.class));
Toast.makeText(this, "Number of widgets: "+ids.length, Toast.LENGTH_LONG).show();
You need to store that information yourself. 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 (usually) 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);
}
I know it's an old question, but looking at this today I saw that there are a couple of problems with the accepted answer from #larsona1:
if the user cleared the shared preferences - there's still widget, but the app won't know about it.
if the user regret between "add widget" and before pressing "ok" - onEnabled will be called anyway, and a widget will be registered in the home screen even though there is no widget, and no way to remove it later. (it may be a bug in ADT home launcher).
I found a solution to the first problem.
No shared preferences are needed at all, since it's unreliable anyway. It has to be checked in runtime.
// in some class you define a static variable, say in S.java
static boolean sWidgetMayExist = true;
In your widget provider:
// MyAppWidgetProvider.java
// to respond to runtime changes, when widgets are added and removed
#Override
public void onEnabled(Context context) {
super.onEnabled(context);
S.sWidgetMayExist = true;
}
#Override
public void onDisabled(Context context) {
super.onDisabled(context);
S.sWidgetMayExist = true;
}
And, in your service code add this:
AppWidgetManager manager = null;
RemoteViews views = null;
ComponentName widgetComponent = null;
// ..and in your update thread
if (!S.sWidgetMayExist) { return; }
if (manager == null || widgetComponent == null) {
widgetComponent = new ComponentName(c,
MyAppWidgetProvider.class);
manager = AppWidgetManager.getInstance(c);
}
if (manager.getAppWidgetIds(widgetComponent) == null) {
S.sWidgetMayExist = false;
}
#Waza_Be is right as looking at the "AppWidgetIds" list to know the number of active widgets (those installed on your homescreen) is the correct way to know this information.
However, keep in mind that you SHOULD don't have to look at this by yourself.
Check the official Android documentation for best practice about widgets :
https://developer.android.com/guide/topics/appwidgets/index.html#AppWidgetProvider
The right approach is to override only the onUpdate() method and iterate through the list of "active" widgets :
public class ExampleAppWidgetProvider extends AppWidgetProvider {
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
final int N = appWidgetIds.length;
// Perform this loop procedure for each App Widget that belongs to this provider
for (int i=0; i<N; i++) {
int appWidgetId = appWidgetIds[i];
// Create an Intent to launch ExampleActivity
Intent intent = new Intent(context, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
// Get the layout for the App Widget and attach an on-click listener
// to the button
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
views.setOnClickPendingIntent(R.id.button, pendingIntent);
// Tell the AppWidgetManager to perform an update on the current app widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
}
And as your own widget provider overrides AppWidgetProvider, you will NOT go into the onUpdate() method if you have no widgets active on the home screen!
See the onReceive() code of Android AppWidgetProvider that checks already for you that "appWidgetIds.length > 0":
public void onReceive(Context context, Intent intent) {
// Protect against rogue update broadcasts (not really a security issue,
// just filter bad broacasts out so subclasses are less likely to crash).
String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds != null && appWidgetIds.length > 0) {
this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
}
}
}
(...)
}
What about the following:
boolean widgetExists(Context context, int appWidgetId) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
AppWidgetProviderInfo info = appWidgetManager.getAppWidgetInfo(appWidgetId);
return (info != null);
}
From the docs for appWidgetManager.getAppWidgetInfo():
If the appWidgetId has not been bound to a provider yet, or you don't have access to that appWidgetId, null is returned.

ViewFlipper in app widgets

I'm playing around building a new widget and was looking at the Android app widget documentation, in particular the section on which widget classes were supported. I noticed that ViewFlipper is supported, but I'm struggling to find any examples on how to use one in a home screen widget. In particular, I'm wondering if its possible to manually trigger swapping views. In an activity this looks relatively straightforward, one example being hooking up the onclick listener of a button to call the showNext() of the flipper.
The RemoteViews object has showNext and showPrevious methods but I'm not sure I understand how to hook them up to an event fired from the user interacting with the widget. Can anyone provide examples of when these methods might be called?
It looks like buttons in widgets can only be wired up to intents rather than code to exercise the flipper. If this restriction is true, then is the only use of a view flipper in an app widget for auto flipping of views?
Say you have 2 buttons: LEFT and RIGHT. First you would attach pending intent to each (here it's triggered from Service#onStart:
#Override
public void onStart(Intent intent, int startId) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this.getApplicationContext());
int[] allWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
// add listeners for every widget registered
for (int widgetId : allWidgetIds) {
addClickListeners(appWidgetManager, widgetId, root);
}
stopSelf();
}
protected void addClickListeners(AppWidgetManager appWidgetManager, int widgetId, RemoteViews root) {
root.setOnClickPendingIntent(R.id.left, getNavigationIntent(widgetId, R.id.left));
root.setOnClickPendingIntent(R.id.right, getNavigationIntent(widgetId, R.id.right));
}
protected PendingIntent getNavigationIntent(int widgetId, final int id) {
Intent clickIntent = new Intent(this, WidgetProvider.class);
clickIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
clickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
clickIntent.putExtra(TRIGGER, id);
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, clickIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
return pendingIntent;
}
Then, in AppWidgetProvider do
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Bundle extras = intent.getExtras();
Integer id = (Integer) (extras == null ? null : extras.get(TRIGGER));
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action) && id != null) {
int widgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, 0);
onNavigate(context, widgetId, id);
} else {
super.onReceive(context, intent);
}
}
protected void onNavigate(Context context, Integer widgetId, Integer id) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
RemoteViews root = new RemoteViews(context.getPackageName(), R.layout.app_widget);
if (id == R.id.left) {
root.showPrevious(R.id.scroll);
} else {
root.showNext(R.id.scroll);
}
appWidgetManager.updateAppWidget(widgetId, root);
}
This should do it. Now the problem is - this will only work in API 11+ and I just found the hard way that root.setInt(R.id.scroll, "setDisplayedChild", pos) will not work in API 7.

Widget not updated on launcher restart

I've got a widget which will update itself whenever there's a configuration change (such as screen orientation), and whenever the phone is unlocked. This process involves setting onClick handlers for the buttons on my widget. This works well, however I have found that there's a usage case which causes my app to not respond to onClick events. This particular case is whenever the launcher restarts itself.
Is there a way to detect when a launcher restarts, so I can update my widget manually? Or is there another way to ensure onClick handlers are not lost?
Turns out I was spamming new RemoteViews() when I should have just called it once to produce the view, and then referred to that one instance when required. In my solution, I have a class variable which stores this single RemoteView instance, and a getter to access it.
Proposal by #Glitch might not work for certain cases, especially app widget with ListView. This is because ListView will get very slow (Try to scroll the ListView) after appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, list_id) had been called several time.
My guess is, the single RemoteView instance will keep all its executed instruction in a list. Over the time, the instruction list will grow. Every time appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, list_id), the large instruction list will be executed all over again.
My proposed solution is as follow. However, I believe it will only work on certain device, as not all devices will receive same broadcast message during launcher restarting.
#SuppressLint("NewApi")
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals("com.sec.android.widgetapp.APPWIDGET_RESIZE")) {
// http://stackoverflow.com/questions/17396045/how-to-catch-widget-size-changes-on-devices-where-onappwidgetoptionschanged-not
handleTouchWiz(context, intent);
// Possible launcher restart.
handleLauncherRestart(context, intent);
} else if (action.equals("android.appwidget.action.APPWIDGET_UPDATE_OPTIONS")) {
// Possible launcher restart.
handleLauncherRestart(context, intent);
}
super.onReceive(context, intent);
}
private void handleLauncherRestart(Context context, Intent intent) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
updateAppWidget(context, appWidgetManager, appWidgetId);
}
private void handleTouchWiz(Context context, Intent intent) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int appWidgetId = intent.getIntExtra("widgetId", 0);
int widgetSpanX = intent.getIntExtra("widgetspanx", 0);
int widgetSpanY = intent.getIntExtra("widgetspany", 0);
if (appWidgetId > 0 && widgetSpanX > 0 && widgetSpanY > 0) {
Bundle newOptions = new Bundle();
// We have to convert these numbers for future use
// http://stackoverflow.com/questions/10008521/appwidget-size-calculation
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, widgetSpanY * 74 - 2);
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, widgetSpanX * 74 - 2);
} else {
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, widgetSpanY * 70 - 30);
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, widgetSpanX * 70 - 30);
}
onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
}
}

Categories

Resources