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.lang.ref.WeakReference;
20import java.util.List;
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.SparseArray;
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    private final Handler mHandler;
62    private final int mHostId;
63    private final Callbacks mCallbacks;
64    private final SparseArray<AppWidgetHostView> mViews = new SparseArray<>();
65    private OnClickHandler mOnClickHandler;
66
67    static class Callbacks extends IAppWidgetHost.Stub {
68        private final WeakReference<Handler> mWeakHandler;
69
70        public Callbacks(Handler handler) {
71            mWeakHandler = new WeakReference<>(handler);
72        }
73
74        public void updateAppWidget(int appWidgetId, RemoteViews views) {
75            if (isLocalBinder() && views != null) {
76                views = views.clone();
77            }
78            Handler handler = mWeakHandler.get();
79            if (handler == null) {
80                return;
81            }
82            Message msg = handler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views);
83            msg.sendToTarget();
84        }
85
86        public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) {
87            if (isLocalBinder() && info != null) {
88                info = info.clone();
89            }
90            Handler handler = mWeakHandler.get();
91            if (handler == null) {
92                return;
93            }
94            Message msg = handler.obtainMessage(HANDLE_PROVIDER_CHANGED,
95                    appWidgetId, 0, info);
96            msg.sendToTarget();
97        }
98
99        public void providersChanged() {
100            Handler handler = mWeakHandler.get();
101            if (handler == null) {
102                return;
103            }
104            handler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget();
105        }
106
107        public void viewDataChanged(int appWidgetId, int viewId) {
108            Handler handler = mWeakHandler.get();
109            if (handler == null) {
110                return;
111            }
112            Message msg = handler.obtainMessage(HANDLE_VIEW_DATA_CHANGED,
113                    appWidgetId, viewId);
114            msg.sendToTarget();
115        }
116
117        private static boolean isLocalBinder() {
118            return Process.myPid() == Binder.getCallingPid();
119        }
120    }
121
122    class UpdateHandler extends Handler {
123        public UpdateHandler(Looper looper) {
124            super(looper);
125        }
126
127        public void handleMessage(Message msg) {
128            switch (msg.what) {
129                case HANDLE_UPDATE: {
130                    updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
131                    break;
132                }
133                case HANDLE_PROVIDER_CHANGED: {
134                    onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj);
135                    break;
136                }
137                case HANDLE_PROVIDERS_CHANGED: {
138                    onProvidersChanged();
139                    break;
140                }
141                case HANDLE_VIEW_DATA_CHANGED: {
142                    viewDataChanged(msg.arg1, msg.arg2);
143                    break;
144                }
145            }
146        }
147    }
148
149    public AppWidgetHost(Context context, int hostId) {
150        this(context, hostId, null, context.getMainLooper());
151    }
152
153    /**
154     * @hide
155     */
156    public AppWidgetHost(Context context, int hostId, OnClickHandler handler, Looper looper) {
157        mContextOpPackageName = context.getOpPackageName();
158        mHostId = hostId;
159        mOnClickHandler = handler;
160        mHandler = new UpdateHandler(looper);
161        mCallbacks = new Callbacks(mHandler);
162        mDisplayMetrics = context.getResources().getDisplayMetrics();
163        bindService();
164    }
165
166    private static void bindService() {
167        synchronized (sServiceLock) {
168            if (sService == null) {
169                IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
170                sService = IAppWidgetService.Stub.asInterface(b);
171            }
172        }
173    }
174
175    /**
176     * Start receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity
177     * becomes visible, i.e. from onStart() in your Activity.
178     */
179    public void startListening() {
180        final int[] idsToUpdate;
181        synchronized (mViews) {
182            int N = mViews.size();
183            idsToUpdate = new int[N];
184            for (int i = 0; i < N; i++) {
185                idsToUpdate[i] = mViews.keyAt(i);
186            }
187        }
188        List<PendingHostUpdate> updates;
189        try {
190            updates = sService.startListening(
191                    mCallbacks, mContextOpPackageName, mHostId, idsToUpdate).getList();
192        }
193        catch (RemoteException e) {
194            throw new RuntimeException("system server dead?", e);
195        }
196
197        int N = updates.size();
198        for (int i = 0; i < N; i++) {
199            PendingHostUpdate update = updates.get(i);
200            switch (update.type) {
201                case PendingHostUpdate.TYPE_VIEWS_UPDATE:
202                    updateAppWidgetView(update.appWidgetId, update.views);
203                    break;
204                case PendingHostUpdate.TYPE_PROVIDER_CHANGED:
205                    onProviderChanged(update.appWidgetId, update.widgetInfo);
206                    break;
207                case PendingHostUpdate.TYPE_VIEW_DATA_CHANGED:
208                    viewDataChanged(update.appWidgetId, update.viewId);
209            }
210        }
211    }
212
213    /**
214     * Stop receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity is
215     * no longer visible, i.e. from onStop() in your Activity.
216     */
217    public void stopListening() {
218        try {
219            sService.stopListening(mContextOpPackageName, mHostId);
220        }
221        catch (RemoteException e) {
222            throw new RuntimeException("system server dead?", e);
223        }
224    }
225
226    /**
227     * Get a appWidgetId for a host in the calling process.
228     *
229     * @return a appWidgetId
230     */
231    public int allocateAppWidgetId() {
232        try {
233            return sService.allocateAppWidgetId(mContextOpPackageName, mHostId);
234        }
235        catch (RemoteException e) {
236            throw new RuntimeException("system server dead?", e);
237        }
238    }
239
240    /**
241     * Starts an app widget provider configure activity for result on behalf of the caller.
242     * Use this method if the provider is in another profile as you are not allowed to start
243     * an activity in another profile. You can optionally provide a request code that is
244     * returned in {@link Activity#onActivityResult(int, int, android.content.Intent)} and
245     * an options bundle to be passed to the started activity.
246     * <p>
247     * Note that the provided app widget has to be bound for this method to work.
248     * </p>
249     *
250     * @param activity The activity from which to start the configure one.
251     * @param appWidgetId The bound app widget whose provider's config activity to start.
252     * @param requestCode Optional request code retuned with the result.
253     * @param intentFlags Optional intent flags.
254     *
255     * @throws android.content.ActivityNotFoundException If the activity is not found.
256     *
257     * @see AppWidgetProviderInfo#getProfile()
258     */
259    public final void startAppWidgetConfigureActivityForResult(@NonNull Activity activity,
260            int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options) {
261        try {
262            IntentSender intentSender = sService.createAppWidgetConfigIntentSender(
263                    mContextOpPackageName, appWidgetId, intentFlags);
264            if (intentSender != null) {
265                activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0,
266                        options);
267            } else {
268                throw new ActivityNotFoundException();
269            }
270        } catch (IntentSender.SendIntentException e) {
271            throw new ActivityNotFoundException();
272        } catch (RemoteException e) {
273            throw new RuntimeException("system server dead?", e);
274        }
275    }
276
277    /**
278     * Gets a list of all the appWidgetIds that are bound to the current host
279     */
280    public int[] getAppWidgetIds() {
281        try {
282            if (sService == null) {
283                bindService();
284            }
285            return sService.getAppWidgetIdsForHost(mContextOpPackageName, mHostId);
286        } catch (RemoteException e) {
287            throw new RuntimeException("system server dead?", e);
288        }
289    }
290
291    /**
292     * Stop listening to changes for this AppWidget.
293     */
294    public void deleteAppWidgetId(int appWidgetId) {
295        synchronized (mViews) {
296            mViews.remove(appWidgetId);
297            try {
298                sService.deleteAppWidgetId(mContextOpPackageName, appWidgetId);
299            }
300            catch (RemoteException e) {
301                throw new RuntimeException("system server dead?", e);
302            }
303        }
304    }
305
306    /**
307     * Remove all records about this host from the AppWidget manager.
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 void deleteHost() {
315        try {
316            sService.deleteHost(mContextOpPackageName, mHostId);
317        }
318        catch (RemoteException e) {
319            throw new RuntimeException("system server dead?", e);
320        }
321    }
322
323    /**
324     * Remove all records about all hosts for your package.
325     * <ul>
326     *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
327     *   <li>Call this to have the AppWidget manager release all resources associated with your
328     *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
329     * </ul>
330     */
331    public static void deleteAllHosts() {
332        try {
333            sService.deleteAllHosts();
334        }
335        catch (RemoteException e) {
336            throw new RuntimeException("system server dead?", e);
337        }
338    }
339
340    /**
341     * Create the AppWidgetHostView for the given widget.
342     * The AppWidgetHost retains a pointer to the newly-created View.
343     */
344    public final AppWidgetHostView createView(Context context, int appWidgetId,
345            AppWidgetProviderInfo appWidget) {
346        AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
347        view.setOnClickHandler(mOnClickHandler);
348        view.setAppWidget(appWidgetId, appWidget);
349        synchronized (mViews) {
350            mViews.put(appWidgetId, view);
351        }
352        RemoteViews views;
353        try {
354            views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
355        } catch (RemoteException e) {
356            throw new RuntimeException("system server dead?", e);
357        }
358        view.updateAppWidget(views);
359
360        return view;
361    }
362
363    /**
364     * Called to create the AppWidgetHostView.  Override to return a custom subclass if you
365     * need it.  {@more}
366     */
367    protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
368            AppWidgetProviderInfo appWidget) {
369        return new AppWidgetHostView(context, mOnClickHandler);
370    }
371
372    /**
373     * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
374     */
375    protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
376        AppWidgetHostView v;
377
378        // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the
379        // AppWidgetService, which doesn't have our context, hence we need to do the
380        // conversion here.
381        appWidget.updateDimensions(mDisplayMetrics);
382        synchronized (mViews) {
383            v = mViews.get(appWidgetId);
384        }
385        if (v != null) {
386            v.resetAppWidget(appWidget);
387        }
388    }
389
390    /**
391     * Called when the set of available widgets changes (ie. widget containing packages
392     * are added, updated or removed, or widget components are enabled or disabled.)
393     */
394    protected void onProvidersChanged() {
395        // Does nothing
396    }
397
398    void updateAppWidgetView(int appWidgetId, RemoteViews views) {
399        AppWidgetHostView v;
400        synchronized (mViews) {
401            v = mViews.get(appWidgetId);
402        }
403        if (v != null) {
404            v.updateAppWidget(views);
405        }
406    }
407
408    void viewDataChanged(int appWidgetId, int viewId) {
409        AppWidgetHostView v;
410        synchronized (mViews) {
411            v = mViews.get(appWidgetId);
412        }
413        if (v != null) {
414            v.viewDataChanged(viewId);
415        }
416    }
417
418    /**
419     * Clear the list of Views that have been created by this AppWidgetHost.
420     */
421    protected void clearViews() {
422        synchronized (mViews) {
423            mViews.clear();
424        }
425    }
426}
427
428
429