1/*
2 * Copyright (C) 2006 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 android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.os.IBinder;
23import android.os.RemoteException;
24import android.os.ServiceManager;
25import android.util.DisplayMetrics;
26import android.util.TypedValue;
27import android.widget.RemoteViews;
28
29import com.android.internal.appwidget.IAppWidgetService;
30
31import java.lang.ref.WeakReference;
32import java.util.List;
33import java.util.WeakHashMap;
34
35/**
36 * Updates AppWidget state; gets information about installed AppWidget providers and other
37 * AppWidget related state.
38 */
39public class AppWidgetManager {
40    static final String TAG = "AppWidgetManager";
41
42    /**
43     * Send this from your {@link AppWidgetHost} activity when you want to pick an AppWidget to display.
44     * The AppWidget picker activity will be launched.
45     * <p>
46     * You must supply the following extras:
47     * <table>
48     *   <tr>
49     *     <td>{@link #EXTRA_APPWIDGET_ID}</td>
50     *     <td>A newly allocated appWidgetId, which will be bound to the AppWidget provider
51     *         once the user has selected one.</td>
52     *  </tr>
53     * </table>
54     *
55     * <p>
56     * The system will respond with an onActivityResult call with the following extras in
57     * the intent:
58     * <table>
59     *   <tr>
60     *     <td>{@link #EXTRA_APPWIDGET_ID}</td>
61     *     <td>The appWidgetId that you supplied in the original intent.</td>
62     *  </tr>
63     * </table>
64     * <p>
65     * When you receive the result from the AppWidget pick activity, if the resultCode is
66     * {@link android.app.Activity#RESULT_OK}, an AppWidget has been selected.  You should then
67     * check the AppWidgetProviderInfo for the returned AppWidget, and if it has one, launch its configuration
68     * activity.  If {@link android.app.Activity#RESULT_CANCELED} is returned, you should delete
69     * the appWidgetId.
70     *
71     * @see #ACTION_APPWIDGET_CONFIGURE
72     */
73    public static final String ACTION_APPWIDGET_PICK = "android.appwidget.action.APPWIDGET_PICK";
74
75    /**
76     * Sent when it is time to configure your AppWidget while it is being added to a host.
77     * This action is not sent as a broadcast to the AppWidget provider, but as a startActivity
78     * to the activity specified in the {@link AppWidgetProviderInfo AppWidgetProviderInfo meta-data}.
79     *
80     * <p>
81     * The intent will contain the following extras:
82     * <table>
83     *   <tr>
84     *     <td>{@link #EXTRA_APPWIDGET_ID}</td>
85     *     <td>The appWidgetId to configure.</td>
86     *  </tr>
87     * </table>
88     *
89     * <p>If you return {@link android.app.Activity#RESULT_OK} using
90     * {@link android.app.Activity#setResult Activity.setResult()}, the AppWidget will be added,
91     * and you will receive an {@link #ACTION_APPWIDGET_UPDATE} broadcast for this AppWidget.
92     * If you return {@link android.app.Activity#RESULT_CANCELED}, the host will cancel the add
93     * and not display this AppWidget, and you will receive a {@link #ACTION_APPWIDGET_DELETED} broadcast.
94     */
95    public static final String ACTION_APPWIDGET_CONFIGURE = "android.appwidget.action.APPWIDGET_CONFIGURE";
96
97    /**
98     * An intent extra that contains one appWidgetId.
99     * <p>
100     * The value will be an int that can be retrieved like this:
101     * {@sample frameworks/base/tests/appwidgets/AppWidgetHostTest/src/com/android/tests/appwidgethost/AppWidgetHostActivity.java getExtra_EXTRA_APPWIDGET_ID}
102     */
103    public static final String EXTRA_APPWIDGET_ID = "appWidgetId";
104
105    /**
106     * An intent extra that contains multiple appWidgetIds.
107     * <p>
108     * The value will be an int array that can be retrieved like this:
109     * {@sample frameworks/base/tests/appwidgets/AppWidgetHostTest/src/com/android/tests/appwidgethost/TestAppWidgetProvider.java getExtra_EXTRA_APPWIDGET_IDS}
110     */
111    public static final String EXTRA_APPWIDGET_IDS = "appWidgetIds";
112
113    /**
114     * An intent extra to pass to the AppWidget picker containing a {@link java.util.List} of
115     * {@link AppWidgetProviderInfo} objects to mix in to the list of AppWidgets that are
116     * installed.  (This is how the launcher shows the search widget).
117     */
118    public static final String EXTRA_CUSTOM_INFO = "customInfo";
119
120    /**
121     * An intent extra to pass to the AppWidget picker containing a {@link java.util.List} of
122     * {@link android.os.Bundle} objects to mix in to the list of AppWidgets that are
123     * installed.  It will be added to the extras object on the {@link android.content.Intent}
124     * that is returned from the picker activity.
125     *
126     * {@more}
127     */
128    public static final String EXTRA_CUSTOM_EXTRAS = "customExtras";
129
130    /**
131     * A sentiel value that the AppWidget manager will never return as a appWidgetId.
132     */
133    public static final int INVALID_APPWIDGET_ID = 0;
134
135    /**
136     * Sent when it is time to update your AppWidget.
137     *
138     * <p>This may be sent in response to a new instance for this AppWidget provider having
139     * been instantiated, the requested {@link AppWidgetProviderInfo#updatePeriodMillis update interval}
140     * having lapsed, or the system booting.
141     *
142     * <p>
143     * The intent will contain the following extras:
144     * <table>
145     *   <tr>
146     *     <td>{@link #EXTRA_APPWIDGET_IDS}</td>
147     *     <td>The appWidgetIds to update.  This may be all of the AppWidgets created for this
148     *     provider, or just a subset.  The system tries to send updates for as few AppWidget
149     *     instances as possible.</td>
150     *  </tr>
151     * </table>
152     *
153     * @see AppWidgetProvider#onUpdate AppWidgetProvider.onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
154     */
155    public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE";
156
157    /**
158     * Sent when an instance of an AppWidget is deleted from its host.
159     *
160     * @see AppWidgetProvider#onDeleted AppWidgetProvider.onDeleted(Context context, int[] appWidgetIds)
161     */
162    public static final String ACTION_APPWIDGET_DELETED = "android.appwidget.action.APPWIDGET_DELETED";
163
164    /**
165     * Sent when an instance of an AppWidget is removed from the last host.
166     *
167     * @see AppWidgetProvider#onEnabled AppWidgetProvider.onEnabled(Context context)
168     */
169    public static final String ACTION_APPWIDGET_DISABLED = "android.appwidget.action.APPWIDGET_DISABLED";
170
171    /**
172     * Sent when an instance of an AppWidget is added to a host for the first time.
173     * This broadcast is sent at boot time if there is a AppWidgetHost installed with
174     * an instance for this provider.
175     *
176     * @see AppWidgetProvider#onEnabled AppWidgetProvider.onEnabled(Context context)
177     */
178    public static final String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED";
179
180    /**
181     * Field for the manifest meta-data tag.
182     *
183     * @see AppWidgetProviderInfo
184     */
185    public static final String META_DATA_APPWIDGET_PROVIDER = "android.appwidget.provider";
186
187    static WeakHashMap<Context, WeakReference<AppWidgetManager>> sManagerCache =
188        new WeakHashMap<Context, WeakReference<AppWidgetManager>>();
189    static IAppWidgetService sService;
190
191    Context mContext;
192
193    private DisplayMetrics mDisplayMetrics;
194
195    /**
196     * Get the AppWidgetManager instance to use for the supplied {@link android.content.Context
197     * Context} object.
198     */
199    public static AppWidgetManager getInstance(Context context) {
200        synchronized (sManagerCache) {
201            if (sService == null) {
202                IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
203                sService = IAppWidgetService.Stub.asInterface(b);
204            }
205
206            WeakReference<AppWidgetManager> ref = sManagerCache.get(context);
207            AppWidgetManager result = null;
208            if (ref != null) {
209                result = ref.get();
210            }
211            if (result == null) {
212                result = new AppWidgetManager(context);
213                sManagerCache.put(context, new WeakReference<AppWidgetManager>(result));
214            }
215            return result;
216        }
217    }
218
219    private AppWidgetManager(Context context) {
220        mContext = context;
221        mDisplayMetrics = context.getResources().getDisplayMetrics();
222    }
223
224    /**
225     * Set the RemoteViews to use for the specified appWidgetIds.
226     *
227     * Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should
228     * contain a complete representation of the widget. For performing partial widget updates, see
229     * {@link #partiallyUpdateAppWidget(int[], RemoteViews)}.
230     *
231     * <p>
232     * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
233     * and outside of the handler.
234     * This method will only work when called from the uid that owns the AppWidget provider.
235     *
236     * @param appWidgetIds     The AppWidget instances for which to set the RemoteViews.
237     * @param views         The RemoteViews object to show.
238     */
239    public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
240        try {
241            sService.updateAppWidgetIds(appWidgetIds, views);
242        }
243        catch (RemoteException e) {
244            throw new RuntimeException("system server dead?", e);
245        }
246    }
247
248    /**
249     * Set the RemoteViews to use for the specified appWidgetId.
250     *
251     * Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should
252     * contain a complete representation of the widget. For performing partial widget updates, see
253     * {@link #partiallyUpdateAppWidget(int, RemoteViews)}.
254     *
255     * <p>
256     * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
257     * and outside of the handler.
258     * This method will only work when called from the uid that owns the AppWidget provider.
259     *
260     * @param appWidgetId      The AppWidget instance for which to set the RemoteViews.
261     * @param views         The RemoteViews object to show.
262     */
263    public void updateAppWidget(int appWidgetId, RemoteViews views) {
264        updateAppWidget(new int[] { appWidgetId }, views);
265    }
266
267    /**
268     * Perform an incremental update or command on the widget(s) specified by appWidgetIds.
269     *
270     * This update  differs from {@link #updateAppWidget(int[], RemoteViews)} in that the
271     * RemoteViews object which is passed is understood to be an incomplete representation of the
272     * widget, and hence is not cached by the AppWidgetService. Note that because these updates are
273     * not cached, any state that they modify that is not restored by restoreInstanceState will not
274     * persist in the case that the widgets are restored using the cached version in
275     * AppWidgetService.
276     *
277     * Use with {@link RemoteViews#showNext(int)}, {@link RemoteViews#showPrevious(int)},
278     * {@link RemoteViews#setScrollPosition(int, int)} and similar commands.
279     *
280     * <p>
281     * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
282     * and outside of the handler.
283     * This method will only work when called from the uid that owns the AppWidget provider.
284     *
285     * @param appWidgetIds     The AppWidget instances for which to set the RemoteViews.
286     * @param views            The RemoteViews object containing the incremental update / command.
287     */
288    public void partiallyUpdateAppWidget(int[] appWidgetIds, RemoteViews views) {
289        try {
290            sService.partiallyUpdateAppWidgetIds(appWidgetIds, views);
291        } catch (RemoteException e) {
292            throw new RuntimeException("system server dead?", e);
293        }
294    }
295
296    /**
297     * Perform an incremental update or command on the widget specified by appWidgetId.
298     *
299     * This update  differs from {@link #updateAppWidget(int, RemoteViews)} in that the RemoteViews
300     * object which is passed is understood to be an incomplete representation of the widget, and
301     * hence is not cached by the AppWidgetService. Note that because these updates are not cached,
302     * any state that they modify that is not restored by restoreInstanceState will not persist in
303     * the case that the widgets are restored using the cached version in AppWidgetService.
304     *
305     * Use with {@link RemoteViews#showNext(int)}, {@link RemoteViews#showPrevious(int)},
306     * {@link RemoteViews#setScrollPosition(int, int)} and similar commands.
307     *
308     * <p>
309     * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
310     * and outside of the handler.
311     * This method will only work when called from the uid that owns the AppWidget provider.
312     *
313     * @param appWidgetId      The AppWidget instance for which to set the RemoteViews.
314     * @param views            The RemoteViews object containing the incremental update / command.
315     */
316    public void partiallyUpdateAppWidget(int appWidgetId, RemoteViews views) {
317        partiallyUpdateAppWidget(new int[] { appWidgetId }, views);
318    }
319
320    /**
321     * Set the RemoteViews to use for all AppWidget instances for the supplied AppWidget provider.
322     *
323     * <p>
324     * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
325     * and outside of the handler.
326     * This method will only work when called from the uid that owns the AppWidget provider.
327     *
328     * @param provider      The {@link ComponentName} for the {@link
329     * android.content.BroadcastReceiver BroadcastReceiver} provider
330     *                      for your AppWidget.
331     * @param views         The RemoteViews object to show.
332     */
333    public void updateAppWidget(ComponentName provider, RemoteViews views) {
334        try {
335            sService.updateAppWidgetProvider(provider, views);
336        }
337        catch (RemoteException e) {
338            throw new RuntimeException("system server dead?", e);
339        }
340    }
341
342    /**
343     * Notifies the specified collection view in all the specified AppWidget instances
344     * to invalidate their currently data.
345     *
346     * @param appWidgetIds  The AppWidget instances for which to notify of view data changes.
347     * @param viewId        The collection view id.
348     */
349    public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
350        try {
351            sService.notifyAppWidgetViewDataChanged(appWidgetIds, viewId);
352        }
353        catch (RemoteException e) {
354            throw new RuntimeException("system server dead?", e);
355        }
356    }
357
358    /**
359     * Notifies the specified collection view in all the specified AppWidget instance
360     * to invalidate it's currently data.
361     *
362     * @param appWidgetId  The AppWidget instance for which to notify of view data changes.
363     * @param viewId        The collection view id.
364     */
365    public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) {
366        notifyAppWidgetViewDataChanged(new int[] { appWidgetId }, viewId);
367    }
368
369    /**
370     * Return a list of the AppWidget providers that are currently installed.
371     */
372    public List<AppWidgetProviderInfo> getInstalledProviders() {
373        try {
374            List<AppWidgetProviderInfo> providers = sService.getInstalledProviders();
375            for (AppWidgetProviderInfo info : providers) {
376                // Converting complex to dp.
377                info.minWidth =
378                        TypedValue.complexToDimensionPixelSize(info.minWidth, mDisplayMetrics);
379                info.minHeight =
380                        TypedValue.complexToDimensionPixelSize(info.minHeight, mDisplayMetrics);
381                info.minResizeWidth =
382                    TypedValue.complexToDimensionPixelSize(info.minResizeWidth, mDisplayMetrics);
383                info.minResizeHeight =
384                    TypedValue.complexToDimensionPixelSize(info.minResizeHeight, mDisplayMetrics);
385            }
386            return providers;
387        }
388        catch (RemoteException e) {
389            throw new RuntimeException("system server dead?", e);
390        }
391    }
392
393    /**
394     * Get the available info about the AppWidget.
395     *
396     * @return A appWidgetId.  If the appWidgetId has not been bound to a provider yet, or
397     * you don't have access to that appWidgetId, null is returned.
398     */
399    public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
400        try {
401            AppWidgetProviderInfo info = sService.getAppWidgetInfo(appWidgetId);
402            if (info != null) {
403                // Converting complex to dp.
404                info.minWidth =
405                        TypedValue.complexToDimensionPixelSize(info.minWidth, mDisplayMetrics);
406                info.minHeight =
407                        TypedValue.complexToDimensionPixelSize(info.minHeight, mDisplayMetrics);
408                info.minResizeWidth =
409                    TypedValue.complexToDimensionPixelSize(info.minResizeWidth, mDisplayMetrics);
410                info.minResizeHeight =
411                    TypedValue.complexToDimensionPixelSize(info.minResizeHeight, mDisplayMetrics);
412            }
413            return info;
414        }
415        catch (RemoteException e) {
416            throw new RuntimeException("system server dead?", e);
417        }
418    }
419
420    /**
421     * Set the component for a given appWidgetId.
422     *
423     * <p class="note">You need the APPWIDGET_LIST permission.  This method is to be used by the
424     * AppWidget picker.
425     *
426     * @param appWidgetId     The AppWidget instance for which to set the RemoteViews.
427     * @param provider      The {@link android.content.BroadcastReceiver} that will be the AppWidget
428     *                      provider for this AppWidget.
429     */
430    public void bindAppWidgetId(int appWidgetId, ComponentName provider) {
431        try {
432            sService.bindAppWidgetId(appWidgetId, provider);
433        }
434        catch (RemoteException e) {
435            throw new RuntimeException("system server dead?", e);
436        }
437    }
438
439    /**
440     * Binds the RemoteViewsService for a given appWidgetId and intent.
441     *
442     * The appWidgetId specified must already be bound to the calling AppWidgetHost via
443     * {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
444     *
445     * @param appWidgetId   The AppWidget instance for which to bind the RemoteViewsService.
446     * @param intent        The intent of the service which will be providing the data to the
447     *                      RemoteViewsAdapter.
448     * @param connection    The callback interface to be notified when a connection is made or lost.
449     * @hide
450     */
451    public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) {
452        try {
453            sService.bindRemoteViewsService(appWidgetId, intent, connection);
454        }
455        catch (RemoteException e) {
456            throw new RuntimeException("system server dead?", e);
457        }
458    }
459
460    /**
461     * Unbinds the RemoteViewsService for a given appWidgetId and intent.
462     *
463     * The appWidgetId specified muse already be bound to the calling AppWidgetHost via
464     * {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
465     *
466     * @param appWidgetId   The AppWidget instance for which to bind the RemoteViewsService.
467     * @param intent        The intent of the service which will be providing the data to the
468     *                      RemoteViewsAdapter.
469     * @hide
470     */
471    public void unbindRemoteViewsService(int appWidgetId, Intent intent) {
472        try {
473            sService.unbindRemoteViewsService(appWidgetId, intent);
474        }
475        catch (RemoteException e) {
476            throw new RuntimeException("system server dead?", e);
477        }
478    }
479
480    /**
481     * Get the list of appWidgetIds that have been bound to the given AppWidget
482     * provider.
483     *
484     * @param provider The {@link android.content.BroadcastReceiver} that is the
485     *            AppWidget provider to find appWidgetIds for.
486     */
487    public int[] getAppWidgetIds(ComponentName provider) {
488        try {
489            return sService.getAppWidgetIds(provider);
490        }
491        catch (RemoteException e) {
492            throw new RuntimeException("system server dead?", e);
493        }
494    }
495}
496
497