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