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