AppWidgetHost.java revision 7a96f3c917e0001ee739b65da37b2fadec7d7765
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.annotation.NonNull;
23import android.annotation.Nullable;
24import android.app.Activity;
25import android.content.ActivityNotFoundException;
26import android.content.Context;
27import android.content.IntentSender;
28import android.os.Binder;
29import android.os.Bundle;
30import android.os.Handler;
31import android.os.IBinder;
32import android.os.Looper;
33import android.os.Message;
34import android.os.Process;
35import android.os.RemoteException;
36import android.os.ServiceManager;
37import android.util.DisplayMetrics;
38import android.util.TypedValue;
39import android.widget.RemoteViews;
40import android.widget.RemoteViews.OnClickHandler;
41
42import com.android.internal.appwidget.IAppWidgetHost;
43import com.android.internal.appwidget.IAppWidgetService;
44
45/**
46 * AppWidgetHost provides the interaction with the AppWidget service for apps,
47 * like the home screen, that want to embed AppWidgets in their UI.
48 */
49public class AppWidgetHost {
50
51    static final int HANDLE_UPDATE = 1;
52    static final int HANDLE_PROVIDER_CHANGED = 2;
53    static final int HANDLE_PROVIDERS_CHANGED = 3;
54    static final int HANDLE_VIEW_DATA_CHANGED = 4;
55
56    final static Object sServiceLock = new Object();
57    static IAppWidgetService sService;
58    private DisplayMetrics mDisplayMetrics;
59
60    private String mContextOpPackageName;
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        mContextOpPackageName = context.getOpPackageName();
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            updatedIds = sService.startListening(mCallbacks, mContextOpPackageName, mHostId,
158                    updatedViews);
159        }
160        catch (RemoteException e) {
161            throw new RuntimeException("system server dead?", e);
162        }
163
164        final int N = updatedIds.length;
165        for (int i = 0; i < N; i++) {
166            updateAppWidgetView(updatedIds[i], updatedViews.get(i));
167        }
168    }
169
170    /**
171     * Stop receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity is
172     * no longer visible, i.e. from onStop() in your Activity.
173     */
174    public void stopListening() {
175        try {
176            sService.stopListening(mContextOpPackageName, mHostId);
177        }
178        catch (RemoteException e) {
179            throw new RuntimeException("system server dead?", e);
180        }
181
182        // This is here because keyguard needs it since it'll be switching users after this call.
183        // If it turns out other apps need to call this often, we should re-think how this works.
184        clearViews();
185    }
186
187    /**
188     * Get a appWidgetId for a host in the calling process.
189     *
190     * @return a appWidgetId
191     */
192    public int allocateAppWidgetId() {
193        try {
194            return sService.allocateAppWidgetId(mContextOpPackageName, mHostId);
195        }
196        catch (RemoteException e) {
197            throw new RuntimeException("system server dead?", e);
198        }
199    }
200
201    /**
202     * Starts an app widget provider configure activity for result on behalf of the caller.
203     * Use this method if the provider is in another profile as you are not allowed to start
204     * an activity in another profile. You can optionally provide a request code that is
205     * returned in {@link Activity#onActivityResult(int, int, android.content.Intent)} and
206     * an options bundle to be passed to the started activity.
207     * <p>
208     * Note that the provided app widget has to be bound for this method to work.
209     * </p>
210     *
211     * @param activity The activity from which to start the configure one.
212     * @param appWidgetId The bound app widget whose provider's config activity to start.
213     * @param requestCode Optional request code retuned with the result.
214     * @param intentFlags Optional intent flags.
215     *
216     * @throws android.content.ActivityNotFoundException If the activity is not found.
217     *
218     * @see AppWidgetProviderInfo#getProfile()
219     */
220    public final void startAppWidgetConfigureActivityForResult(@NonNull Activity activity,
221            int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options) {
222        try {
223            IntentSender intentSender = sService.createAppWidgetConfigIntentSender(
224                    mContextOpPackageName, appWidgetId, intentFlags);
225            if (intentSender != null) {
226                activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0,
227                        options);
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(mContextOpPackageName, 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(mContextOpPackageName, 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(mContextOpPackageName, 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(context, 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(mContextOpPackageName, 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