• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.appwidget;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.Activity;
22 import android.content.ActivityNotFoundException;
23 import android.content.Context;
24 import android.content.IntentSender;
25 import android.content.pm.PackageManager;
26 import android.os.Binder;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.IBinder;
30 import android.os.Looper;
31 import android.os.Message;
32 import android.os.Process;
33 import android.os.RemoteException;
34 import android.os.ServiceManager;
35 import android.util.DisplayMetrics;
36 import android.util.SparseArray;
37 import android.widget.RemoteViews;
38 import android.widget.RemoteViews.OnClickHandler;
39 
40 import com.android.internal.R;
41 import com.android.internal.appwidget.IAppWidgetHost;
42 import com.android.internal.appwidget.IAppWidgetService;
43 
44 import java.lang.ref.WeakReference;
45 import 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  */
51 public 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 
Callbacks(Handler handler)73         public Callbacks(Handler handler) {
74             mWeakHandler = new WeakReference<>(handler);
75         }
76 
updateAppWidget(int appWidgetId, RemoteViews views)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 
providerChanged(int appWidgetId, AppWidgetProviderInfo info)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 
providersChanged()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 
viewDataChanged(int appWidgetId, int viewId)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 
isLocalBinder()120         private static boolean isLocalBinder() {
121             return Process.myPid() == Binder.getCallingPid();
122         }
123     }
124 
125     class UpdateHandler extends Handler {
UpdateHandler(Looper looper)126         public UpdateHandler(Looper looper) {
127             super(looper);
128         }
129 
handleMessage(Message msg)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 
AppWidgetHost(Context context, int hostId)152     public AppWidgetHost(Context context, int hostId) {
153         this(context, hostId, null, context.getMainLooper());
154     }
155 
156     /**
157      * @hide
158      */
AppWidgetHost(Context context, int hostId, OnClickHandler handler, Looper looper)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 
bindService(Context context)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      */
startListening()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      */
stopListening()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      */
allocateAppWidgetId()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      */
startAppWidgetConfigureActivityForResult(@onNull Activity activity, int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options)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      */
getAppWidgetIds()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      */
deleteAppWidgetId(int appWidgetId)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      */
deleteHost()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      */
deleteAllHosts()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      */
createView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget)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      */
onCreateView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget)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      */
onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget)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      */
onProvidersChanged()428     protected void onProvidersChanged() {
429         // Does nothing
430     }
431 
updateAppWidgetView(int appWidgetId, RemoteViews views)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 
viewDataChanged(int appWidgetId, int viewId)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      */
clearViews()455     protected void clearViews() {
456         synchronized (mViews) {
457             mViews.clear();
458         }
459     }
460 }
461 
462 
463