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