19

I'm trying to turn the stock ICS launcher into a standalone app. I'm nearly there - the only things not working are the search icon and dropping widgets onto the screen, which causes a crash.

The crash is because the stock launcher uses appWidgetManager.bindAppWidgetId(appWidgetId, componentName); to add widgets, which apparently only system apps have permission to do.

So my question is, what is the correct way for a non-system app to add widgets and acheive the same UI experience as the stock ICS launcher?

Community
  • 1
  • 1
Timmmm
  • 88,195
  • 71
  • 364
  • 509

4 Answers4

13

Timmmm,

Your issue is that you are looking to the wrong object. You can't really control the AppWidgetManager. Its not your job, its the System's. What you CAN do is control an AppWidgetHost, it just requires a few semantics. Here are the basics.

EDIT: Extra Background on the Widget Binding Process

The AppWidgetManager is a singleton object that runs when the System is started. This means that every instance of every launcher uses the same AppWidgetManager. What differentiates them is their AppWidgetHost and the RemoteViews they are currently holding. The AppWidgetManager basically keeps a list of all of the active hosts and the widgets they are holding. An AppWidgetHost is not a priveleged object. That is, any activity may have a single host. Thus, an entire application may be nothing but Widgets, if they so choose.

When you instantiate the Host, you must then add Views to it. So, basically it is a list of child Views with no mandatory parental bounds, except what your Activity gives it. First, you ask for an ID (via myHost.allocateAppWidgetId()). Then you use your Pick Widget Activity/Dialog. The Dialog returns the WidgetInfo. The View is retrieved when you ask the Host to create the View (via createView) with the WidgetInfo and the ID you asked for. It then asks the widget for its RemoteView.

Finally, you bind the widget by placing the View in your Activity as a Child. This is done via the addView() method of the ViewGroup that holds all of your Widgets.

The Process in Action (EDITED)

First, you have to make sure you have this in your android manifest:

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

Next, you have to create an AppWidgetHost (I extend my own for my launcher). The key to the Host is to keep a reference to the AppWidgetManager via AppWidgetManager.getInstance();.

AppWidgetHost myHost = new AppWidgetHost(context, SOME_NUMERICAL_CONSTANT_AS_AN_ID);

Now, get your ID:

myHost.allocateAppWidgetId()

The next step is done by whatever method you use to get the widget info. Most times it is returned via an Intent through onActivityResult. Now, all you really have to do is use the appInfo and create the view. The WidgetId is normally provided by the pick widget activity result.

AppWidgetProviderInfo withWidgetInfo 
        = AppWidgetManager.getInstance().getAppWidgetInfo(forWidgetId);
AppWidgetHostView hostView 
        = myWidgetHost.createView(myContext, forWidgetId, withWidgetInfo);
hostView.setAppWidget(forWidgetId, withWidgetInfo);

Now you just bind the View as a child to whatever you want to bind it to.

myViewGroup.addView(hostView);

Of course, you always have to consider where and how to place it, etc. Also, you have to make sure that your AppWidgetHost is listening before you start adding widgets.

myHost.startListening()

To Summarize

The Widget binding process spans many methods and steps, but all occurs through the AppWidgetHost. Because Widgets are coded outside of your namespace you don't have any control except for where you put them and how you size the View. Since they are ultimately code that runs in your space but outside of your control, the AppWidgetManager acts as a neutral mediator, while the AppWidgetHost serves as the facilitator on your app's behalf. Once this is understood, your task is simple. The steps above are all the required steps for any custom launcher (including my own).

EDIT: Final Clarification

The ICS Launcher does this as well. The appWidgetManager they use is just a wrapper housing the AppWidgetHost and the calls to the AppWidgetManager. I forget that very little of this is explained on the Android Development Central website.

Hope this helps! Let me know if you need anymore details.

FuzzicalLogic

Fuzzical Logic
  • 12,947
  • 2
  • 30
  • 58
  • Thanks for the answer, but unfortunately it doesn't seem to solve my problem: 1. I think your use of BIND_APPWIDGET has no effect, because non-system apps cannot get that permission (see the link in my question). 2. I'm using my own "pick widget activity" -- the one built into the ICS launcher. I may have misunderstood, but I'm beginning to think that this is impossible. – Timmmm Mar 20 '12 at 14:46
  • What permissions can be given depends on the phone/device's ROM. For instance, my Samsung Moment gives me that permission (non-rooted). The Motorola Xoom (rooted) will not give that permission, but yells if I do not have it. – Fuzzical Logic Mar 21 '12 at 02:56
  • Wow really? That's ridiculous! It seems that it is intended that it should not be given though. Thank you for answering anyway. – Timmmm Mar 21 '12 at 02:59
  • The "Pick Widget" Activity should still return an ID and Info for you to choose. That means the code above does still apply. This code is used AFTER you have picked your widget and have the appropriate information. (For instance, in my launcher, I get the information and use the code above to display a resize dialog and THEN add it) – Fuzzical Logic Mar 21 '12 at 19:10
  • I'm not sure I follow. Is the pick widget activity not the list of activities? I.e. [this](http://thedailybuggle.s3.amazonaws.com/wp-content/uploads/2011/11/android_widgets_list.png)? I want to avoid that since then users will have to choose the widget twice! – Timmmm Mar 22 '12 at 11:13
  • What I'm saying is that your custom Pick Widget Activity should still return the Widget Info (including having reserved an ID for itself) to your app. The ID is gotten by requesting one. The bind is done with "createView" not "bindAppWidgetId". You request the View from the Widget with the ID for the AppWidgetHost... Then the Host talks to the AppWidgetManager and gives it ALL of that information. You can not display a widget (in other words) without the AppWidgetHost, the View, or the ID. And THEN you can use the AppWidgetManager. – Fuzzical Logic Mar 22 '12 at 12:40
  • Another way to explain it is: only a system app can instantiate the AppWidgetManager, but ANY app can have an AppWidgetHost that communicates with the AppWidgetManager. The Host just has to have the prerequisite information first. That's what the above solution provides. – Fuzzical Logic Mar 22 '12 at 12:47
  • @Fuzzical Logic This is the error log: User 10107 does not have android.permission.BIND_APPWIDGET. com.android.internal.appwidget.IAppWidgetService$Stub$Proxy.bindAppWidgetId(IAppWidgetService.java:569)android.appwidget.AppWidgetManager.bindAppWidgetId(AppWidgetManager.java:432) E/AndroidRuntime( 3174): at com.android.ooziclauncher2.Launcher.addAppWidgetFromDrop(Launcher.java:1618) So, it the problem of: AppWidgetManager.getInstance(this).bindAppWidgetId(appWidgetId, info.componentName); which you haven't quoted. Thanks. – herbertD Jun 19 '12 at 01:50
  • And I saw another solution is to build apk as a system application in firmware or push it into /system/app with root privilege. – herbertD Jun 19 '12 at 01:58
  • I already explained... you cannot bind widget. If you want to root and push as firmware that does not fit the terms of the OP, certainly go ahead, but the OPs terms were using the stock launcher. This means that bindAppWidgetId will not work and the only way to accomplish binding is by utilizing the method above. This is because AppWidgetManager is not yours to control. As an app, you control AppWidgetHost – Fuzzical Logic Jun 19 '12 at 07:26
  • 2
    @Fuzzical Logic Your method just apply for the old AppWidgetPickActivity, not the ICS. Because the AppWidgetPickActivity also use the mAppWidgetManager.bindAppWidgetId(mAppWidgetId, intent.getComponent()); while it resides in System app: Settings, and the Settings have the privilege to bind appwidget. – herbertD Jul 09 '12 at 07:15
  • 2
    @herbertD: No offense intended, but that's exactly what I am saying. "BIND_APPWIDGET" is a System permission and only used by the system. Settings is allowed IF the Settings app was installed as part of the ROM. Yes, my method is the "old way" but the objects still exists in ICS and it works on launchers that utilize 4.0. In fact, that was the reason the OP made this post... because BIND_APPWIDGET is a **system** permission **only**. My solution does not work for you because you use a **custom** widget picker. If you want to find a solution, either change your picker or make a new Question. – Fuzzical Logic Jul 09 '12 at 11:29
  • Further, some ROMs allow BIND_APPWIDGET to be given and some do not. So, to play it safe, many launchers will accept the discarded warning and place the permission anyway. The solution is not in the permission, it is in the code process which is detailed. To focus on the permission only does not make it any less relavant – Fuzzical Logic Jul 09 '12 at 11:32
  • Hi @FuzzicalLogic : Can you tell me what other ways are there to create AppWidgetInfo if we donot want to launch the WidgetPicker Activity to do it? – Ritesh Mahato Feb 08 '17 at 10:12
8

I now know the definitive answer. In Android 4.0, you can't do it. I ended up making my users pick the widget twice, which sucks, but there is no way around it.

In Android 4.1 they fixed the problem!

SDK apps can now host widgets and don't have to use the rubbish widget picker API! You can look into the Jellybean Launcher2 source code for details, but basically, when you first try to bind a widget, Android will pop up a dialog box saying "Do you want to allow this app to bind widgets", and then the user can decide to give it permission or not.

I'm not sure why they went for the modal permission-granting dialog box rather than the all-permissions-on-install model they've used for everything else, but whatever, it works!

Now we just have to wait 4 or 5 years until everyone has Android 4.1 or greater!

Timmmm
  • 88,195
  • 71
  • 364
  • 509
4

I just found this tutorial on how to add appwidgets to normal apps, which might help: http://coderender.blogspot.com/2012/01/hosting-android-widgets-my.html

This tutorial still uses the "AppWidget Picker" list, so it might not work for you since ICS has the widgets picker inside the app drawer itself.

Still, was worth to mention since tutorials on hosting widgets are very rare :)

Cheers,
Yuvi

YuviDroid
  • 1,546
  • 11
  • 12
2

Fuzzical Logic,with your code below,

AppWidgetProviderInfo withWidgetInfo 
        = AppWidgetManager.getInstance().getAppWidgetInfo(forWidgetId);
AppWidgetHostView hostView 
        = myWidgetHost.createView(myContext, forWidgetId, withWidgetInfo);
hostView.setAppWidget(forWidgetId, withWidgetInfo);

if have not the permission of bind_widget,widgethost got nothing,cus withwidgetinfo is null,widgethost create nothing.

Flexo
  • 87,323
  • 22
  • 191
  • 272
tomoroho
  • 51
  • 3
  • 1
    for `getAppWidgetInfo` to return an object the `forWidgetId` must be already bound: either by picking the widget through the system picker or calling `bindAppWidgetIdIfAllowed` or if that returns false just `startActivityForResult(AppWidgetManager.ACTION_APPWIDGET_BIND)` with the right params (see documentation). – TWiStErRob Oct 18 '14 at 22:09