• 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.compat.annotation.UnsupportedAppUsage;
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.Build;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.IBinder;
32 import android.os.Looper;
33 import android.os.Message;
34 import android.os.Process;
35 import android.os.RemoteException;
36 import android.os.ServiceManager;
37 import android.util.DisplayMetrics;
38 import android.util.Log;
39 import android.util.SparseArray;
40 import android.widget.RemoteViews;
41 import android.widget.RemoteViews.InteractionHandler;
42 
43 import com.android.internal.R;
44 import com.android.internal.appwidget.IAppWidgetHost;
45 import com.android.internal.appwidget.IAppWidgetService;
46 
47 import java.lang.ref.WeakReference;
48 import java.util.List;
49 
50 /**
51  * AppWidgetHost provides the interaction with the AppWidget service for apps,
52  * like the home screen, that want to embed AppWidgets in their UI.
53  */
54 public class AppWidgetHost {
55 
56     private static final String TAG = "AppWidgetHost";
57 
58     static final int HANDLE_UPDATE = 1;
59     static final int HANDLE_PROVIDER_CHANGED = 2;
60     static final int HANDLE_PROVIDERS_CHANGED = 3;
61     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
62     static final int HANDLE_VIEW_DATA_CHANGED = 4;
63     static final int HANDLE_APP_WIDGET_REMOVED = 5;
64     static final int HANDLE_VIEW_UPDATE_DEFERRED = 6;
65 
66     final static Object sServiceLock = new Object();
67     @UnsupportedAppUsage
68     static IAppWidgetService sService;
69     static boolean sServiceInitialized = false;
70     private DisplayMetrics mDisplayMetrics;
71 
72     private String mContextOpPackageName;
73     @UnsupportedAppUsage
74     private final Handler mHandler;
75     private final int mHostId;
76     private final Callbacks mCallbacks;
77     private final SparseArray<AppWidgetHostListener> mListeners = new SparseArray<>();
78     private InteractionHandler mInteractionHandler;
79 
80     static class Callbacks extends IAppWidgetHost.Stub {
81         private final WeakReference<Handler> mWeakHandler;
82 
Callbacks(Handler handler)83         public Callbacks(Handler handler) {
84             mWeakHandler = new WeakReference<>(handler);
85         }
86 
updateAppWidget(int appWidgetId, RemoteViews views)87         public void updateAppWidget(int appWidgetId, RemoteViews views) {
88             if (isLocalBinder() && views != null) {
89                 views = views.clone();
90             }
91             Handler handler = mWeakHandler.get();
92             if (handler == null) {
93                 return;
94             }
95             Message msg = handler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views);
96             msg.sendToTarget();
97         }
98 
providerChanged(int appWidgetId, AppWidgetProviderInfo info)99         public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) {
100             if (isLocalBinder() && info != null) {
101                 info = info.clone();
102             }
103             Handler handler = mWeakHandler.get();
104             if (handler == null) {
105                 return;
106             }
107             Message msg = handler.obtainMessage(HANDLE_PROVIDER_CHANGED,
108                     appWidgetId, 0, info);
109             msg.sendToTarget();
110         }
111 
appWidgetRemoved(int appWidgetId)112         public void appWidgetRemoved(int appWidgetId) {
113             Handler handler = mWeakHandler.get();
114             if (handler == null) {
115                 return;
116             }
117             handler.obtainMessage(HANDLE_APP_WIDGET_REMOVED, appWidgetId, 0).sendToTarget();
118         }
119 
providersChanged()120         public void providersChanged() {
121             Handler handler = mWeakHandler.get();
122             if (handler == null) {
123                 return;
124             }
125             handler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget();
126         }
127 
viewDataChanged(int appWidgetId, int viewId)128         public void viewDataChanged(int appWidgetId, int viewId) {
129             Handler handler = mWeakHandler.get();
130             if (handler == null) {
131                 return;
132             }
133             Message msg = handler.obtainMessage(HANDLE_VIEW_DATA_CHANGED,
134                     appWidgetId, viewId);
135             msg.sendToTarget();
136         }
137 
updateAppWidgetDeferred(int appWidgetId)138         public void updateAppWidgetDeferred(int appWidgetId) {
139             Handler handler = mWeakHandler.get();
140             if (handler == null) {
141                 return;
142             }
143             Message msg = handler.obtainMessage(HANDLE_VIEW_UPDATE_DEFERRED, appWidgetId, 0, null);
144             msg.sendToTarget();
145         }
146 
isLocalBinder()147         private static boolean isLocalBinder() {
148             return Process.myPid() == Binder.getCallingPid();
149         }
150     }
151 
152     class UpdateHandler extends Handler {
UpdateHandler(Looper looper)153         public UpdateHandler(Looper looper) {
154             super(looper);
155         }
156 
handleMessage(Message msg)157         public void handleMessage(Message msg) {
158             switch (msg.what) {
159                 case HANDLE_UPDATE: {
160                     updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
161                     break;
162                 }
163                 case HANDLE_APP_WIDGET_REMOVED: {
164                     dispatchOnAppWidgetRemoved(msg.arg1);
165                     break;
166                 }
167                 case HANDLE_PROVIDER_CHANGED: {
168                     onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj);
169                     break;
170                 }
171                 case HANDLE_PROVIDERS_CHANGED: {
172                     onProvidersChanged();
173                     break;
174                 }
175                 case HANDLE_VIEW_DATA_CHANGED: {
176                     viewDataChanged(msg.arg1, msg.arg2);
177                     break;
178                 }
179                 case HANDLE_VIEW_UPDATE_DEFERRED: {
180                     updateAppWidgetDeferred(msg.arg1);
181                     break;
182                 }
183             }
184         }
185     }
186 
AppWidgetHost(Context context, int hostId)187     public AppWidgetHost(Context context, int hostId) {
188         this(context, hostId, null, context.getMainLooper());
189     }
190 
191     @Nullable
getListener(final int appWidgetId)192     private AppWidgetHostListener getListener(final int appWidgetId) {
193         AppWidgetHostListener tempListener = null;
194         synchronized (mListeners) {
195             tempListener = mListeners.get(appWidgetId);
196         }
197         return tempListener;
198     }
199 
200     /**
201      * @hide
202      */
203     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
AppWidgetHost(Context context, int hostId, InteractionHandler handler, Looper looper)204     public AppWidgetHost(Context context, int hostId, InteractionHandler handler, Looper looper) {
205         mContextOpPackageName = context.getOpPackageName();
206         mHostId = hostId;
207         mInteractionHandler = handler;
208         mHandler = new UpdateHandler(looper);
209         mCallbacks = new Callbacks(mHandler);
210         mDisplayMetrics = context.getResources().getDisplayMetrics();
211         bindService(context);
212     }
213 
bindService(Context context)214     private static void bindService(Context context) {
215         synchronized (sServiceLock) {
216             if (sServiceInitialized) {
217                 return;
218             }
219             sServiceInitialized = true;
220             PackageManager packageManager = context.getPackageManager();
221             if (!packageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
222                     && !context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
223                 return;
224             }
225             IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
226             sService = IAppWidgetService.Stub.asInterface(b);
227         }
228     }
229 
230     /**
231      * Start receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity
232      * becomes visible, i.e. from onStart() in your Activity.
233      */
startListening()234     public void startListening() {
235         if (sService == null) {
236             return;
237         }
238         final int[] idsToUpdate;
239         synchronized (mListeners) {
240             int n = mListeners.size();
241             idsToUpdate = new int[n];
242             for (int i = 0; i < n; i++) {
243                 idsToUpdate[i] = mListeners.keyAt(i);
244             }
245         }
246         List<PendingHostUpdate> updates;
247         try {
248             updates = sService.startListening(
249                     mCallbacks, mContextOpPackageName, mHostId, idsToUpdate).getList();
250         }
251         catch (RemoteException e) {
252             throw new RuntimeException("system server dead?", e);
253         }
254 
255         int N = updates.size();
256         for (int i = 0; i < N; i++) {
257             PendingHostUpdate update = updates.get(i);
258             switch (update.type) {
259                 case PendingHostUpdate.TYPE_VIEWS_UPDATE:
260                     updateAppWidgetView(update.appWidgetId, update.views);
261                     break;
262                 case PendingHostUpdate.TYPE_PROVIDER_CHANGED:
263                     onProviderChanged(update.appWidgetId, update.widgetInfo);
264                     break;
265                 case PendingHostUpdate.TYPE_VIEW_DATA_CHANGED:
266                     viewDataChanged(update.appWidgetId, update.viewId);
267                     break;
268                 case PendingHostUpdate.TYPE_APP_WIDGET_REMOVED:
269                     dispatchOnAppWidgetRemoved(update.appWidgetId);
270                     break;
271             }
272         }
273     }
274 
275     /**
276      * Stop receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity is
277      * no longer visible, i.e. from onStop() in your Activity.
278      */
stopListening()279     public void stopListening() {
280         if (sService == null) {
281             return;
282         }
283         try {
284             sService.stopListening(mContextOpPackageName, mHostId);
285         }
286         catch (RemoteException e) {
287             throw new RuntimeException("system server dead?", e);
288         }
289     }
290 
291     /**
292      * Get a appWidgetId for a host in the calling process.
293      *
294      * @return a appWidgetId
295      */
allocateAppWidgetId()296     public int allocateAppWidgetId() {
297         if (sService == null) {
298             return -1;
299         }
300         try {
301             return sService.allocateAppWidgetId(mContextOpPackageName, mHostId);
302         }
303         catch (RemoteException e) {
304             throw new RuntimeException("system server dead?", e);
305         }
306     }
307 
308     /**
309      * Returns an {@link IntentSender} for starting the configuration activity for the widget.
310      *
311      * @return The {@link IntentSender} or null if service is currently unavailable
312      *
313      * @throws android.content.ActivityNotFoundException If configuration activity is not found.
314      *
315      * @see #startAppWidgetConfigureActivityForResult
316      *
317      * @hide
318      */
319     @Nullable
getIntentSenderForConfigureActivity(int appWidgetId, int intentFlags)320     public final IntentSender getIntentSenderForConfigureActivity(int appWidgetId,
321             int intentFlags)  {
322         if (sService == null) {
323             return null;
324         }
325 
326         IntentSender intentSender;
327         try {
328             intentSender = sService.createAppWidgetConfigIntentSender(mContextOpPackageName,
329                     appWidgetId, intentFlags);
330         } catch (RemoteException e) {
331             throw new RuntimeException("system server dead?", e);
332         }
333 
334         if (intentSender == null) {
335             throw new ActivityNotFoundException();
336         }
337         return intentSender;
338     }
339 
340     /**
341      * Starts an app widget provider configure activity for result on behalf of the caller.
342      * Use this method if the provider is in another profile as you are not allowed to start
343      * an activity in another profile. You can optionally provide a request code that is
344      * returned in {@link Activity#onActivityResult(int, int, android.content.Intent)} and
345      * an options bundle to be passed to the started activity.
346      * <p>
347      * Note that the provided app widget has to be bound for this method to work.
348      * </p>
349      *
350      * @param activity The activity from which to start the configure one.
351      * @param appWidgetId The bound app widget whose provider's config activity to start.
352      * @param requestCode Optional request code retuned with the result.
353      * @param intentFlags Optional intent flags.
354      *
355      * @throws android.content.ActivityNotFoundException If the activity is not found.
356      *
357      * @see AppWidgetProviderInfo#getProfile()
358      */
startAppWidgetConfigureActivityForResult(@onNull Activity activity, int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options)359     public final void startAppWidgetConfigureActivityForResult(@NonNull Activity activity,
360             int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options) {
361         if (sService == null) {
362             return;
363         }
364         try {
365             IntentSender intentSender = getIntentSenderForConfigureActivity(appWidgetId,
366                     intentFlags);
367             activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0, options);
368         } catch (IntentSender.SendIntentException e) {
369             throw new ActivityNotFoundException();
370         }
371     }
372 
373     /**
374      * Set the visibiity of all widgets associated with this host to hidden
375      *
376      * @hide
377      */
setAppWidgetHidden()378     public void setAppWidgetHidden() {
379         if (sService == null) {
380             return;
381         }
382         try {
383             sService.setAppWidgetHidden(mContextOpPackageName, mHostId);
384         } catch (RemoteException e) {
385             throw new RuntimeException("System server dead?", e);
386         }
387     }
388 
389     /**
390      * Set the host's interaction handler.
391      *
392      * @hide
393      */
setInteractionHandler(InteractionHandler interactionHandler)394     public void setInteractionHandler(InteractionHandler interactionHandler) {
395         mInteractionHandler = interactionHandler;
396     }
397 
398     /**
399      * Gets a list of all the appWidgetIds that are bound to the current host
400      */
getAppWidgetIds()401     public int[] getAppWidgetIds() {
402         if (sService == null) {
403             return new int[0];
404         }
405         try {
406             return sService.getAppWidgetIdsForHost(mContextOpPackageName, mHostId);
407         } catch (RemoteException e) {
408             throw new RuntimeException("system server dead?", e);
409         }
410     }
411 
412     /**
413      * Stop listening to changes for this AppWidget.
414      */
deleteAppWidgetId(int appWidgetId)415     public void deleteAppWidgetId(int appWidgetId) {
416         if (sService == null) {
417             return;
418         }
419         removeListener(appWidgetId);
420         try {
421             sService.deleteAppWidgetId(mContextOpPackageName, appWidgetId);
422         } catch (RemoteException e) {
423             throw new RuntimeException("system server dead?", e);
424         }
425     }
426 
427     /**
428      * Remove all records about this host from the AppWidget manager.
429      * <ul>
430      *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
431      *   <li>Call this to have the AppWidget manager release all resources associated with your
432      *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
433      * </ul>
434      */
deleteHost()435     public void deleteHost() {
436         if (sService == null) {
437             return;
438         }
439         try {
440             sService.deleteHost(mContextOpPackageName, mHostId);
441         }
442         catch (RemoteException e) {
443             throw new RuntimeException("system server dead?", e);
444         }
445     }
446 
447     /**
448      * Remove all records about all hosts for your package.
449      * <ul>
450      *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
451      *   <li>Call this to have the AppWidget manager release all resources associated with your
452      *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
453      * </ul>
454      */
deleteAllHosts()455     public static void deleteAllHosts() {
456         if (sService == null) {
457             return;
458         }
459         try {
460             sService.deleteAllHosts();
461         }
462         catch (RemoteException e) {
463             throw new RuntimeException("system server dead?", e);
464         }
465     }
466 
467     /**
468      * Create the AppWidgetHostView for the given widget.
469      * The AppWidgetHost retains a pointer to the newly-created View.
470      */
createView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget)471     public final AppWidgetHostView createView(Context context, int appWidgetId,
472             AppWidgetProviderInfo appWidget) {
473         if (sService == null) {
474             return null;
475         }
476         AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
477         view.setInteractionHandler(mInteractionHandler);
478         view.setAppWidget(appWidgetId, appWidget);
479         setListener(appWidgetId, view);
480 
481         return view;
482     }
483 
484     /**
485      * Called to create the AppWidgetHostView.  Override to return a custom subclass if you
486      * need it.  {@more}
487      */
onCreateView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget)488     protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
489             AppWidgetProviderInfo appWidget) {
490         return new AppWidgetHostView(context, mInteractionHandler);
491     }
492 
493     /**
494      * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
495      */
onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget)496     protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
497         AppWidgetHostListener v = getListener(appWidgetId);
498 
499         // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the
500         // AppWidgetService, which doesn't have our context, hence we need to do the
501         // conversion here.
502         appWidget.updateDimensions(mDisplayMetrics);
503         if (v != null) {
504             v.onUpdateProviderInfo(appWidget);
505         }
506     }
507 
508     /**
509      * This interface specifies the actions to be performed on the app widget based on the calls
510      * from the service
511      *
512      * @hide
513      */
514     public interface AppWidgetHostListener {
515 
516         /**
517          * This function is called when the service want to reset the app widget provider info
518          * @param appWidget The new app widget provider info
519          *
520          * @hide
521          */
onUpdateProviderInfo(@ullable AppWidgetProviderInfo appWidget)522         void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo appWidget);
523 
524         /**
525          * This function is called when the {@code RemoteViews} of the app widget is updated
526          * @param views The new {@code RemoteViews} to be set for the app widget
527          *
528          * @hide
529          */
updateAppWidget(@ullable RemoteViews views)530         void updateAppWidget(@Nullable RemoteViews views);
531 
532         /**
533          * Called for the listener to handle deferred {@code RemoteViews} updates. Default
534          * implementation is to update the widget directly.
535          * @param packageName The package name used for uid verification on the service side
536          * @param appWidgetId The widget id of the listener
537          *
538          * @hide
539          */
updateAppWidgetDeferred(String packageName, int appWidgetId)540         default void updateAppWidgetDeferred(String packageName, int appWidgetId) {
541             RemoteViews latestViews = null;
542             try {
543                 latestViews = sService.getAppWidgetViews(packageName, appWidgetId);
544             } catch (Exception e) {
545                 Log.e(TAG, "updateAppWidgetDeferred: ", e);
546             }
547             updateAppWidget(latestViews);
548         }
549 
550         /**
551          * This function is called when the view ID is changed for the app widget
552          * @param viewId The new view ID to be be set for the widget
553          *
554          * @hide
555          */
onViewDataChanged(int viewId)556         void onViewDataChanged(int viewId);
557     }
558 
dispatchOnAppWidgetRemoved(int appWidgetId)559     void dispatchOnAppWidgetRemoved(int appWidgetId) {
560         removeListener(appWidgetId);
561         onAppWidgetRemoved(appWidgetId);
562     }
563 
564     /**
565      * Called when the app widget is removed for appWidgetId
566      * @param appWidgetId
567      */
onAppWidgetRemoved(int appWidgetId)568     public void onAppWidgetRemoved(int appWidgetId) {
569         // Does nothing
570     }
571 
572     /**
573      * Called when the set of available widgets changes (ie. widget containing packages
574      * are added, updated or removed, or widget components are enabled or disabled.)
575      */
onProvidersChanged()576     protected void onProvidersChanged() {
577         // Does nothing
578     }
579 
580     /**
581      * Create an AppWidgetHostListener for the given widget.
582      * The AppWidgetHost retains a pointer to the newly-created listener.
583      * @param appWidgetId The ID of the app widget for which to add the listener
584      * @param listener The listener interface that deals with actions towards the widget view
585      * @hide
586      */
setListener(int appWidgetId, @NonNull AppWidgetHostListener listener)587     public void setListener(int appWidgetId, @NonNull AppWidgetHostListener listener) {
588         synchronized (mListeners) {
589             mListeners.put(appWidgetId, listener);
590         }
591         RemoteViews views = null;
592         try {
593             views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
594         } catch (RemoteException e) {
595             throw new RuntimeException("system server dead?", e);
596         }
597         listener.updateAppWidget(views);
598     }
599 
600     /**
601      * Delete the listener for the given widget
602      * @param appWidgetId The ID of the app widget for which the listener is to be deleted
603 
604      * @hide
605      */
removeListener(int appWidgetId)606     public void removeListener(int appWidgetId) {
607         synchronized (mListeners) {
608             mListeners.remove(appWidgetId);
609         }
610     }
611 
updateAppWidgetView(int appWidgetId, RemoteViews views)612     void updateAppWidgetView(int appWidgetId, RemoteViews views) {
613         AppWidgetHostListener v = getListener(appWidgetId);
614         if (v != null) {
615             v.updateAppWidget(views);
616         }
617     }
618 
viewDataChanged(int appWidgetId, int viewId)619     void viewDataChanged(int appWidgetId, int viewId) {
620         AppWidgetHostListener v = getListener(appWidgetId);
621         if (v != null) {
622             v.onViewDataChanged(viewId);
623         }
624     }
625 
updateAppWidgetDeferred(int appWidgetId)626     private void updateAppWidgetDeferred(int appWidgetId) {
627         AppWidgetHostListener v = getListener(appWidgetId);
628         if (v == null) {
629             Log.e(TAG, "updateAppWidgetDeferred: null listener for id: " + appWidgetId);
630             return;
631         }
632         v.updateAppWidgetDeferred(mContextOpPackageName, appWidgetId);
633     }
634 
635     /**
636      * Clear the list of Views that have been created by this AppWidgetHost.
637      */
clearViews()638     protected void clearViews() {
639         synchronized (mListeners) {
640             mListeners.clear();
641         }
642     }
643 }
644 
645 
646