[go: nahoru, domu]

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