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