AppWidgetHost.java revision 976e8bd2017d0263216c62111454438cc0f130e3
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.appwidget;
18
19import java.util.ArrayList;
20import java.util.HashMap;
21
22import android.app.Activity;
23import android.content.ActivityNotFoundException;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentSender;
27import android.os.Binder;
28import android.os.Handler;
29import android.os.IBinder;
30import android.os.Looper;
31import android.os.Message;
32import android.os.Process;
33import android.os.RemoteException;
34import android.os.ServiceManager;
35import android.os.UserHandle;
36import android.util.DisplayMetrics;
37import android.util.TypedValue;
38import android.widget.RemoteViews;
39import android.widget.RemoteViews.OnClickHandler;
40
41import com.android.internal.appwidget.IAppWidgetHost;
42import com.android.internal.appwidget.IAppWidgetService;
43
44/**
45 * AppWidgetHost provides the interaction with the AppWidget service for apps,
46 * like the home screen, that want to embed AppWidgets in their UI.
47 */
48public class AppWidgetHost {
49
50    static final int HANDLE_UPDATE = 1;
51    static final int HANDLE_PROVIDER_CHANGED = 2;
52    static final int HANDLE_PROVIDERS_CHANGED = 3;
53    static final int HANDLE_VIEW_DATA_CHANGED = 4;
54
55    final static Object sServiceLock = new Object();
56    static IAppWidgetService sService;
57    private DisplayMetrics mDisplayMetrics;
58
59    Context mContext;
60    String mPackageName;
61    Handler mHandler;
62    int mHostId;
63    Callbacks mCallbacks = new Callbacks();
64    final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<Integer, AppWidgetHostView>();
65    private OnClickHandler mOnClickHandler;
66
67    class Callbacks extends IAppWidgetHost.Stub {
68        public void updateAppWidget(int appWidgetId, RemoteViews views) {
69            if (isLocalBinder() && views != null) {
70                views = views.clone();
71            }
72            Message msg = mHandler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views);
73            msg.sendToTarget();
74        }
75
76        public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) {
77            if (isLocalBinder() && info != null) {
78                info = info.clone();
79            }
80            Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED,
81                    appWidgetId, 0, info);
82            msg.sendToTarget();
83        }
84
85        public void providersChanged() {
86            mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget();
87        }
88
89        public void viewDataChanged(int appWidgetId, int viewId) {
90            Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED,
91                    appWidgetId, viewId);
92            msg.sendToTarget();
93        }
94    }
95
96    class UpdateHandler extends Handler {
97        public UpdateHandler(Looper looper) {
98            super(looper);
99        }
100
101        public void handleMessage(Message msg) {
102            switch (msg.what) {
103                case HANDLE_UPDATE: {
104                    updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
105                    break;
106                }
107                case HANDLE_PROVIDER_CHANGED: {
108                    onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj);
109                    break;
110                }
111                case HANDLE_PROVIDERS_CHANGED: {
112                    onProvidersChanged();
113                    break;
114                }
115                case HANDLE_VIEW_DATA_CHANGED: {
116                    viewDataChanged(msg.arg1, msg.arg2);
117                    break;
118                }
119            }
120        }
121    }
122
123    public AppWidgetHost(Context context, int hostId) {
124        this(context, hostId, null, context.getMainLooper());
125    }
126
127    /**
128     * @hide
129     */
130    public AppWidgetHost(Context context, int hostId, OnClickHandler handler, Looper looper) {
131        mContext = context;
132        mHostId = hostId;
133        mOnClickHandler = handler;
134        mHandler = new UpdateHandler(looper);
135        mDisplayMetrics = context.getResources().getDisplayMetrics();
136        bindService();
137    }
138
139
140    private static void bindService() {
141        synchronized (sServiceLock) {
142            if (sService == null) {
143                IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
144                sService = IAppWidgetService.Stub.asInterface(b);
145            }
146        }
147    }
148
149    /**
150     * Start receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity
151     * becomes visible, i.e. from onStart() in your Activity.
152     */
153    public void startListening() {
154        int[] updatedIds;
155        ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>();
156        try {
157            if (mPackageName == null) {
158                mPackageName = mContext.getPackageName();
159            }
160            updatedIds = sService.startListening(mCallbacks, mPackageName, mHostId,
161                    updatedViews);
162        }
163        catch (RemoteException e) {
164            throw new RuntimeException("system server dead?", e);
165        }
166
167        final int N = updatedIds.length;
168        for (int i = 0; i < N; i++) {
169            updateAppWidgetView(updatedIds[i], updatedViews.get(i));
170        }
171    }
172
173    /**
174     * Stop receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity is
175     * no longer visible, i.e. from onStop() in your Activity.
176     */
177    public void stopListening() {
178        try {
179            sService.stopListening(mPackageName, mHostId);
180        }
181        catch (RemoteException e) {
182            throw new RuntimeException("system server dead?", e);
183        }
184
185        // This is here because keyguard needs it since it'll be switching users after this call.
186        // If it turns out other apps need to call this often, we should re-think how this works.
187        clearViews();
188    }
189
190    /**
191     * Get a appWidgetId for a host in the calling process.
192     *
193     * @return a appWidgetId
194     */
195    public int allocateAppWidgetId() {
196        try {
197            if (mPackageName == null) {
198                mPackageName = mContext.getPackageName();
199            }
200            return sService.allocateAppWidgetId(mPackageName, mHostId);
201        }
202        catch (RemoteException e) {
203            throw new RuntimeException("system server dead?", e);
204        }
205    }
206
207    /**
208     * Starts an app widget provider configure activity for result on behalf of the caller.
209     * Use this method if the provider is in another profile as you are not allowed to start
210     * an activity in another profile. The provided intent must have action {@link
211     * AppWidgetManager#ACTION_APPWIDGET_CONFIGURE}. The widget id must be specified by
212     * the {@link AppWidgetManager#EXTRA_APPWIDGET_ID} extra. The provider configure
213     * activity must be specified as the component name of the intent via {@link
214     * Intent#setComponent(android.content.ComponentName)}. The user profile under which
215     * the provider operates is specified via the {@link
216     * AppWidgetManager#EXTRA_APPWIDGET_PROVIDER_PROFILE} extra. If a user profile is
217     * not provided, the current user is used. Finally, you can optionally provide a
218     * request code that is returned in {@link Activity#onActivityResult(int, int,
219     * android.content.Intent)}.
220     *
221     * @param activity The activity from which to start the configure one.
222     * @param intent Properly populated intent for launching the configuration activity.
223     * @param requestCode Optional request code retuned with the result.
224     *
225     * @throws android.content.ActivityNotFoundException If the activity is not found.
226     *
227     * @see AppWidgetProviderInfo#getProfile()
228     */
229    public final void startAppWidgetConfigureActivityForResult(Activity activity, Intent intent,
230            int requestCode) {
231        try {
232            IntentSender intentSender = sService.createAppWidgetConfigIntentSender(
233                    mContext.getPackageName(), intent);
234            if (intentSender != null) {
235                activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0);
236            } else {
237                throw new ActivityNotFoundException();
238            }
239        } catch (IntentSender.SendIntentException e) {
240            throw new ActivityNotFoundException();
241        } catch (RemoteException e) {
242            throw new RuntimeException("system server dead?", e);
243        }
244    }
245
246    /**
247     * Gets a list of all the appWidgetIds that are bound to the current host
248     *
249     * @hide
250     */
251    public int[] getAppWidgetIds() {
252        try {
253            if (sService == null) {
254                bindService();
255            }
256            return sService.getAppWidgetIdsForHost(mContext.getPackageName(), mHostId);
257        } catch (RemoteException e) {
258            throw new RuntimeException("system server dead?", e);
259        }
260    }
261
262    private boolean isLocalBinder() {
263        return Process.myPid() == Binder.getCallingPid();
264    }
265
266    /**
267     * Stop listening to changes for this AppWidget.
268     */
269    public void deleteAppWidgetId(int appWidgetId) {
270        synchronized (mViews) {
271            mViews.remove(appWidgetId);
272            try {
273                sService.deleteAppWidgetId(mContext.getPackageName(), appWidgetId);
274            }
275            catch (RemoteException e) {
276                throw new RuntimeException("system server dead?", e);
277            }
278        }
279    }
280
281    /**
282     * Remove all records about this host from the AppWidget manager.
283     * <ul>
284     *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
285     *   <li>Call this to have the AppWidget manager release all resources associated with your
286     *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
287     * </ul>
288     */
289    public void deleteHost() {
290        try {
291            sService.deleteHost(mContext.getPackageName(), mHostId);
292        }
293        catch (RemoteException e) {
294            throw new RuntimeException("system server dead?", e);
295        }
296    }
297
298    /**
299     * Remove all records about all hosts for your package.
300     * <ul>
301     *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
302     *   <li>Call this to have the AppWidget manager release all resources associated with your
303     *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
304     * </ul>
305     */
306    public static void deleteAllHosts() {
307        try {
308            sService.deleteAllHosts();
309        }
310        catch (RemoteException e) {
311            throw new RuntimeException("system server dead?", e);
312        }
313    }
314
315    /**
316     * Create the AppWidgetHostView for the given widget.
317     * The AppWidgetHost retains a pointer to the newly-created View.
318     */
319    public final AppWidgetHostView createView(Context context, int appWidgetId,
320            AppWidgetProviderInfo appWidget) {
321        AppWidgetHostView view = onCreateView(mContext, appWidgetId, appWidget);
322        view.setOnClickHandler(mOnClickHandler);
323        view.setAppWidget(appWidgetId, appWidget);
324        synchronized (mViews) {
325            mViews.put(appWidgetId, view);
326        }
327        RemoteViews views;
328        try {
329            views = sService.getAppWidgetViews(mPackageName, appWidgetId);
330        } catch (RemoteException e) {
331            throw new RuntimeException("system server dead?", e);
332        }
333        view.updateAppWidget(views);
334
335        return view;
336    }
337
338    /**
339     * Called to create the AppWidgetHostView.  Override to return a custom subclass if you
340     * need it.  {@more}
341     */
342    protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
343            AppWidgetProviderInfo appWidget) {
344        return new AppWidgetHostView(context, mOnClickHandler);
345    }
346
347    /**
348     * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
349     */
350    protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
351        AppWidgetHostView v;
352
353        // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the
354        // AppWidgetService, which doesn't have our context, hence we need to do the
355        // conversion here.
356        appWidget.minWidth =
357            TypedValue.complexToDimensionPixelSize(appWidget.minWidth, mDisplayMetrics);
358        appWidget.minHeight =
359            TypedValue.complexToDimensionPixelSize(appWidget.minHeight, mDisplayMetrics);
360        appWidget.minResizeWidth =
361            TypedValue.complexToDimensionPixelSize(appWidget.minResizeWidth, mDisplayMetrics);
362        appWidget.minResizeHeight =
363            TypedValue.complexToDimensionPixelSize(appWidget.minResizeHeight, mDisplayMetrics);
364
365        synchronized (mViews) {
366            v = mViews.get(appWidgetId);
367        }
368        if (v != null) {
369            v.resetAppWidget(appWidget);
370        }
371    }
372
373    /**
374     * Called when the set of available widgets changes (ie. widget containing packages
375     * are added, updated or removed, or widget components are enabled or disabled.)
376     */
377    protected void onProvidersChanged() {
378        // Does nothing
379    }
380
381    void updateAppWidgetView(int appWidgetId, RemoteViews views) {
382        AppWidgetHostView v;
383        synchronized (mViews) {
384            v = mViews.get(appWidgetId);
385        }
386        if (v != null) {
387            v.updateAppWidget(views);
388        }
389    }
390
391    void viewDataChanged(int appWidgetId, int viewId) {
392        AppWidgetHostView v;
393        synchronized (mViews) {
394            v = mViews.get(appWidgetId);
395        }
396        if (v != null) {
397            v.viewDataChanged(viewId);
398        }
399    }
400
401    /**
402     * Clear the list of Views that have been created by this AppWidgetHost.
403     */
404    protected void clearViews() {
405        mViews.clear();
406    }
407}
408
409
410