AppWidgetHost.java revision 6090995951c6e2e4dcf38102f01793f8a94166e1
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.content.Context;
23import android.os.Binder;
24import android.os.Handler;
25import android.os.IBinder;
26import android.os.Looper;
27import android.os.Message;
28import android.os.Process;
29import android.os.RemoteException;
30import android.os.ServiceManager;
31import android.os.UserHandle;
32import android.util.DisplayMetrics;
33import android.util.TypedValue;
34import android.widget.RemoteViews;
35import android.widget.RemoteViews.OnClickHandler;
36
37import com.android.internal.appwidget.IAppWidgetHost;
38import com.android.internal.appwidget.IAppWidgetService;
39
40/**
41 * AppWidgetHost provides the interaction with the AppWidget service for apps,
42 * like the home screen, that want to embed AppWidgets in their UI.
43 */
44public class AppWidgetHost {
45
46    static final int HANDLE_UPDATE = 1;
47    static final int HANDLE_PROVIDER_CHANGED = 2;
48    static final int HANDLE_PROVIDERS_CHANGED = 3;
49    static final int HANDLE_VIEW_DATA_CHANGED = 4;
50
51    final static Object sServiceLock = new Object();
52    static IAppWidgetService sService;
53    private DisplayMetrics mDisplayMetrics;
54
55    Context mContext;
56    String mPackageName;
57    Handler mHandler;
58    int mHostId;
59    Callbacks mCallbacks = new Callbacks();
60    final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<Integer, AppWidgetHostView>();
61    private OnClickHandler mOnClickHandler;
62
63    class Callbacks extends IAppWidgetHost.Stub {
64        public void updateAppWidget(int appWidgetId, RemoteViews views, int userId) {
65            if (isLocalBinder() && views != null) {
66                views = views.clone();
67                views.setUser(new UserHandle(userId));
68            }
69            Message msg = mHandler.obtainMessage(HANDLE_UPDATE, appWidgetId, userId, views);
70            msg.sendToTarget();
71        }
72
73        public void providerChanged(int appWidgetId, AppWidgetProviderInfo info, int userId) {
74            if (isLocalBinder() && info != null) {
75                info = info.clone();
76            }
77            Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED,
78                    appWidgetId, userId, info);
79            msg.sendToTarget();
80        }
81
82        public void providersChanged(int userId) {
83            Message msg = mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED, userId, 0);
84            msg.sendToTarget();
85        }
86
87        public void viewDataChanged(int appWidgetId, int viewId, int userId) {
88            Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED,
89                    appWidgetId, viewId, userId);
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, msg.arg2);
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, (Integer) msg.obj);
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
155        final int userId = mContext.getUserId();
156        try {
157            if (mPackageName == null) {
158                mPackageName = mContext.getPackageName();
159            }
160            updatedIds = sService.startListening(
161                    mCallbacks, mPackageName, mHostId, updatedViews, userId);
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            if (updatedViews.get(i) != null) {
170                updatedViews.get(i).setUser(new UserHandle(userId));
171            }
172            updateAppWidgetView(updatedIds[i], updatedViews.get(i), userId);
173        }
174    }
175
176    /**
177     * Stop receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity is
178     * no longer visible, i.e. from onStop() in your Activity.
179     */
180    public void stopListening() {
181        try {
182            sService.stopListening(mHostId, mContext.getUserId());
183        }
184        catch (RemoteException e) {
185            throw new RuntimeException("system server dead?", e);
186        }
187
188        // This is here because keyguard needs it since it'll be switching users after this call.
189        // If it turns out other apps need to call this often, we should re-think how this works.
190        clearViews();
191    }
192
193    /**
194     * Get a appWidgetId for a host in the calling process.
195     *
196     * @return a appWidgetId
197     */
198    public int allocateAppWidgetId() {
199        try {
200            if (mPackageName == null) {
201                mPackageName = mContext.getPackageName();
202            }
203            return sService.allocateAppWidgetId(mPackageName, mHostId, mContext.getUserId());
204        }
205        catch (RemoteException e) {
206            throw new RuntimeException("system server dead?", e);
207        }
208    }
209
210    /**
211     * Get a appWidgetId for a host in the given package.
212     *
213     * @return a appWidgetId
214     * @hide
215     */
216    public static int allocateAppWidgetIdForPackage(int hostId, int userId, String packageName) {
217        checkCallerIsSystem();
218        try {
219            if (sService == null) {
220                bindService();
221            }
222            return sService.allocateAppWidgetId(packageName, hostId, userId);
223        } catch (RemoteException e) {
224            throw new RuntimeException("system server dead?", e);
225        }
226    }
227
228    /**
229     * Gets a list of all the appWidgetIds that are bound to the current host
230     *
231     * @hide
232     */
233    public int[] getAppWidgetIds() {
234        try {
235            if (sService == null) {
236                bindService();
237            }
238            return sService.getAppWidgetIdsForHost(mHostId, mContext.getUserId());
239        } catch (RemoteException e) {
240            throw new RuntimeException("system server dead?", e);
241        }
242    }
243
244    private static void checkCallerIsSystem() {
245        int uid = Process.myUid();
246        if (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0) {
247            return;
248        }
249        throw new SecurityException("Disallowed call for uid " + uid);
250    }
251
252    private boolean isLocalBinder() {
253        return Process.myPid() == Binder.getCallingPid();
254    }
255
256    /**
257     * Stop listening to changes for this AppWidget.
258     */
259    public void deleteAppWidgetId(int appWidgetId) {
260        synchronized (mViews) {
261            mViews.remove(appWidgetId);
262            try {
263                sService.deleteAppWidgetId(appWidgetId, mContext.getUserId());
264            }
265            catch (RemoteException e) {
266                throw new RuntimeException("system server dead?", e);
267            }
268        }
269    }
270
271    /**
272     * Stop listening to changes for this AppWidget.
273     * @hide
274     */
275    public static void deleteAppWidgetIdForSystem(int appWidgetId, int userId) {
276        checkCallerIsSystem();
277        try {
278            if (sService == null) {
279                bindService();
280            }
281            sService.deleteAppWidgetId(appWidgetId, userId);
282        } catch (RemoteException e) {
283            throw new RuntimeException("system server dead?", e);
284        }
285    }
286
287    /**
288     * Remove all records about this host from the AppWidget manager.
289     * <ul>
290     *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
291     *   <li>Call this to have the AppWidget manager release all resources associated with your
292     *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
293     * </ul>
294     */
295    public void deleteHost() {
296        try {
297            sService.deleteHost(mHostId, mContext.getUserId());
298        }
299        catch (RemoteException e) {
300            throw new RuntimeException("system server dead?", e);
301        }
302    }
303
304    /**
305     * Remove all records about all hosts for your package.
306     * <ul>
307     *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
308     *   <li>Call this to have the AppWidget manager release all resources associated with your
309     *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
310     * </ul>
311     */
312    public static void deleteAllHosts() {
313        deleteAllHosts(UserHandle.myUserId());
314    }
315
316    /**
317     * Private method containing a userId
318     * @hide
319     */
320    public static void deleteAllHosts(int userId) {
321        try {
322            sService.deleteAllHosts(userId);
323        }
324        catch (RemoteException e) {
325            throw new RuntimeException("system server dead?", e);
326        }
327    }
328
329    /**
330     * Create the AppWidgetHostView for the given widget.
331     * The AppWidgetHost retains a pointer to the newly-created View.
332     */
333    public final AppWidgetHostView createView(Context context, int appWidgetId,
334            AppWidgetProviderInfo appWidget) {
335        final int userId = mContext.getUserId();
336        AppWidgetHostView view = onCreateView(mContext, appWidgetId, appWidget);
337        view.setUserId(userId);
338        view.setOnClickHandler(mOnClickHandler);
339        view.setAppWidget(appWidgetId, appWidget);
340        synchronized (mViews) {
341            mViews.put(appWidgetId, view);
342        }
343        RemoteViews views;
344        try {
345            views = sService.getAppWidgetViews(appWidgetId, userId);
346            if (views != null) {
347                views.setUser(new UserHandle(mContext.getUserId()));
348            }
349        } catch (RemoteException e) {
350            throw new RuntimeException("system server dead?", e);
351        }
352        view.updateAppWidget(views);
353
354        return view;
355    }
356
357    /**
358     * Called to create the AppWidgetHostView.  Override to return a custom subclass if you
359     * need it.  {@more}
360     */
361    protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
362            AppWidgetProviderInfo appWidget) {
363        return new AppWidgetHostView(context, mOnClickHandler);
364    }
365
366    /**
367     * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
368     */
369    protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
370        AppWidgetHostView v;
371
372        // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the
373        // AppWidgetService, which doesn't have our context, hence we need to do the
374        // conversion here.
375        appWidget.minWidth =
376            TypedValue.complexToDimensionPixelSize(appWidget.minWidth, mDisplayMetrics);
377        appWidget.minHeight =
378            TypedValue.complexToDimensionPixelSize(appWidget.minHeight, mDisplayMetrics);
379        appWidget.minResizeWidth =
380            TypedValue.complexToDimensionPixelSize(appWidget.minResizeWidth, mDisplayMetrics);
381        appWidget.minResizeHeight =
382            TypedValue.complexToDimensionPixelSize(appWidget.minResizeHeight, mDisplayMetrics);
383
384        synchronized (mViews) {
385            v = mViews.get(appWidgetId);
386        }
387        if (v != null) {
388            v.resetAppWidget(appWidget);
389        }
390    }
391
392    /**
393     * Called when the set of available widgets changes (ie. widget containing packages
394     * are added, updated or removed, or widget components are enabled or disabled.)
395     */
396    protected void onProvidersChanged() {
397        // Does nothing
398    }
399
400    void updateAppWidgetView(int appWidgetId, RemoteViews views, int userId) {
401        AppWidgetHostView v;
402        synchronized (mViews) {
403            v = mViews.get(appWidgetId);
404        }
405        if (v != null) {
406            v.updateAppWidget(views);
407        }
408    }
409
410    void viewDataChanged(int appWidgetId, int viewId, int userId) {
411        AppWidgetHostView v;
412        synchronized (mViews) {
413            v = mViews.get(appWidgetId);
414        }
415        if (v != null) {
416            v.viewDataChanged(viewId);
417        }
418    }
419
420    /**
421     * Clear the list of Views that have been created by this AppWidgetHost.
422     */
423    protected void clearViews() {
424        mViews.clear();
425    }
426}
427
428
429