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      * Starts an app widget provider configure activity for result on behalf of the caller.
310      * Use this method if the provider is in another profile as you are not allowed to start
311      * an activity in another profile. You can optionally provide a request code that is
312      * returned in {@link Activity#onActivityResult(int, int, android.content.Intent)} and
313      * an options bundle to be passed to the started activity.
314      * <p>
315      * Note that the provided app widget has to be bound for this method to work.
316      * </p>
317      *
318      * @param activity The activity from which to start the configure one.
319      * @param appWidgetId The bound app widget whose provider's config activity to start.
320      * @param requestCode Optional request code retuned with the result.
321      * @param intentFlags Optional intent flags.
322      *
323      * @throws android.content.ActivityNotFoundException If the activity is not found.
324      *
325      * @see AppWidgetProviderInfo#getProfile()
326      */
startAppWidgetConfigureActivityForResult(@onNull Activity activity, int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options)327     public final void startAppWidgetConfigureActivityForResult(@NonNull Activity activity,
328             int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options) {
329         if (sService == null) {
330             return;
331         }
332         try {
333             IntentSender intentSender = sService.createAppWidgetConfigIntentSender(
334                     mContextOpPackageName, appWidgetId, intentFlags);
335             if (intentSender != null) {
336                 activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0,
337                         options);
338             } else {
339                 throw new ActivityNotFoundException();
340             }
341         } catch (IntentSender.SendIntentException e) {
342             throw new ActivityNotFoundException();
343         } catch (RemoteException e) {
344             throw new RuntimeException("system server dead?", e);
345         }
346     }
347 
348     /**
349      * Set the visibiity of all widgets associated with this host to hidden
350      *
351      * @hide
352      */
setAppWidgetHidden()353     public void setAppWidgetHidden() {
354         if (sService == null) {
355             return;
356         }
357         try {
358             sService.setAppWidgetHidden(mContextOpPackageName, mHostId);
359         } catch (RemoteException e) {
360             throw new RuntimeException("System server dead?", e);
361         }
362     }
363 
364     /**
365      * Set the host's interaction handler.
366      *
367      * @hide
368      */
setInteractionHandler(InteractionHandler interactionHandler)369     public void setInteractionHandler(InteractionHandler interactionHandler) {
370         mInteractionHandler = interactionHandler;
371     }
372 
373     /**
374      * Gets a list of all the appWidgetIds that are bound to the current host
375      */
getAppWidgetIds()376     public int[] getAppWidgetIds() {
377         if (sService == null) {
378             return new int[0];
379         }
380         try {
381             return sService.getAppWidgetIdsForHost(mContextOpPackageName, mHostId);
382         } catch (RemoteException e) {
383             throw new RuntimeException("system server dead?", e);
384         }
385     }
386 
387     /**
388      * Stop listening to changes for this AppWidget.
389      */
deleteAppWidgetId(int appWidgetId)390     public void deleteAppWidgetId(int appWidgetId) {
391         if (sService == null) {
392             return;
393         }
394         removeListener(appWidgetId);
395         try {
396             sService.deleteAppWidgetId(mContextOpPackageName, appWidgetId);
397         } catch (RemoteException e) {
398             throw new RuntimeException("system server dead?", e);
399         }
400     }
401 
402     /**
403      * Remove all records about this host from the AppWidget manager.
404      * <ul>
405      *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
406      *   <li>Call this to have the AppWidget manager release all resources associated with your
407      *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
408      * </ul>
409      */
deleteHost()410     public void deleteHost() {
411         if (sService == null) {
412             return;
413         }
414         try {
415             sService.deleteHost(mContextOpPackageName, mHostId);
416         }
417         catch (RemoteException e) {
418             throw new RuntimeException("system server dead?", e);
419         }
420     }
421 
422     /**
423      * Remove all records about all hosts for your package.
424      * <ul>
425      *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
426      *   <li>Call this to have the AppWidget manager release all resources associated with your
427      *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
428      * </ul>
429      */
deleteAllHosts()430     public static void deleteAllHosts() {
431         if (sService == null) {
432             return;
433         }
434         try {
435             sService.deleteAllHosts();
436         }
437         catch (RemoteException e) {
438             throw new RuntimeException("system server dead?", e);
439         }
440     }
441 
442     /**
443      * Create the AppWidgetHostView for the given widget.
444      * The AppWidgetHost retains a pointer to the newly-created View.
445      */
createView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget)446     public final AppWidgetHostView createView(Context context, int appWidgetId,
447             AppWidgetProviderInfo appWidget) {
448         if (sService == null) {
449             return null;
450         }
451         AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
452         view.setInteractionHandler(mInteractionHandler);
453         view.setAppWidget(appWidgetId, appWidget);
454         setListener(appWidgetId, view);
455 
456         return view;
457     }
458 
459     /**
460      * Called to create the AppWidgetHostView.  Override to return a custom subclass if you
461      * need it.  {@more}
462      */
onCreateView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget)463     protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
464             AppWidgetProviderInfo appWidget) {
465         return new AppWidgetHostView(context, mInteractionHandler);
466     }
467 
468     /**
469      * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
470      */
onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget)471     protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
472         AppWidgetHostListener v = getListener(appWidgetId);
473 
474         // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the
475         // AppWidgetService, which doesn't have our context, hence we need to do the
476         // conversion here.
477         appWidget.updateDimensions(mDisplayMetrics);
478         if (v != null) {
479             v.onUpdateProviderInfo(appWidget);
480         }
481     }
482 
483     /**
484      * This interface specifies the actions to be performed on the app widget based on the calls
485      * from the service
486      *
487      * @hide
488      */
489     public interface AppWidgetHostListener {
490 
491         /**
492          * This function is called when the service want to reset the app widget provider info
493          * @param appWidget The new app widget provider info
494          *
495          * @hide
496          */
onUpdateProviderInfo(@ullable AppWidgetProviderInfo appWidget)497         void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo appWidget);
498 
499         /**
500          * This function is called when the {@code RemoteViews} of the app widget is updated
501          * @param views The new {@code RemoteViews} to be set for the app widget
502          *
503          * @hide
504          */
updateAppWidget(@ullable RemoteViews views)505         void updateAppWidget(@Nullable RemoteViews views);
506 
507         /**
508          * Called for the listener to handle deferred {@code RemoteViews} updates. Default
509          * implementation is to update the widget directly.
510          * @param packageName The package name used for uid verification on the service side
511          * @param appWidgetId The widget id of the listener
512          *
513          * @hide
514          */
updateAppWidgetDeferred(String packageName, int appWidgetId)515         default void updateAppWidgetDeferred(String packageName, int appWidgetId) {
516             RemoteViews latestViews = null;
517             try {
518                 latestViews = sService.getAppWidgetViews(packageName, appWidgetId);
519             } catch (Exception e) {
520                 Log.e(TAG, "updateAppWidgetDeferred: ", e);
521             }
522             updateAppWidget(latestViews);
523         }
524 
525         /**
526          * This function is called when the view ID is changed for the app widget
527          * @param viewId The new view ID to be be set for the widget
528          *
529          * @hide
530          */
onViewDataChanged(int viewId)531         void onViewDataChanged(int viewId);
532     }
533 
dispatchOnAppWidgetRemoved(int appWidgetId)534     void dispatchOnAppWidgetRemoved(int appWidgetId) {
535         removeListener(appWidgetId);
536         onAppWidgetRemoved(appWidgetId);
537     }
538 
539     /**
540      * Called when the app widget is removed for appWidgetId
541      * @param appWidgetId
542      */
onAppWidgetRemoved(int appWidgetId)543     public void onAppWidgetRemoved(int appWidgetId) {
544         // Does nothing
545     }
546 
547     /**
548      * Called when the set of available widgets changes (ie. widget containing packages
549      * are added, updated or removed, or widget components are enabled or disabled.)
550      */
onProvidersChanged()551     protected void onProvidersChanged() {
552         // Does nothing
553     }
554 
555     /**
556      * Create an AppWidgetHostListener for the given widget.
557      * The AppWidgetHost retains a pointer to the newly-created listener.
558      * @param appWidgetId The ID of the app widget for which to add the listener
559      * @param listener The listener interface that deals with actions towards the widget view
560      * @hide
561      */
setListener(int appWidgetId, @NonNull AppWidgetHostListener listener)562     public void setListener(int appWidgetId, @NonNull AppWidgetHostListener listener) {
563         synchronized (mListeners) {
564             mListeners.put(appWidgetId, listener);
565         }
566         RemoteViews views = null;
567         try {
568             views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
569         } catch (RemoteException e) {
570             throw new RuntimeException("system server dead?", e);
571         }
572         listener.updateAppWidget(views);
573     }
574 
575     /**
576      * Delete the listener for the given widget
577      * @param appWidgetId The ID of the app widget for which the listener is to be deleted
578 
579      * @hide
580      */
removeListener(int appWidgetId)581     public void removeListener(int appWidgetId) {
582         synchronized (mListeners) {
583             mListeners.remove(appWidgetId);
584         }
585     }
586 
updateAppWidgetView(int appWidgetId, RemoteViews views)587     void updateAppWidgetView(int appWidgetId, RemoteViews views) {
588         AppWidgetHostListener v = getListener(appWidgetId);
589         if (v != null) {
590             v.updateAppWidget(views);
591         }
592     }
593 
viewDataChanged(int appWidgetId, int viewId)594     void viewDataChanged(int appWidgetId, int viewId) {
595         AppWidgetHostListener v = getListener(appWidgetId);
596         if (v != null) {
597             v.onViewDataChanged(viewId);
598         }
599     }
600 
updateAppWidgetDeferred(int appWidgetId)601     private void updateAppWidgetDeferred(int appWidgetId) {
602         AppWidgetHostListener v = getListener(appWidgetId);
603         if (v == null) {
604             Log.e(TAG, "updateAppWidgetDeferred: null listener for id: " + appWidgetId);
605             return;
606         }
607         v.updateAppWidgetDeferred(mContextOpPackageName, appWidgetId);
608     }
609 
610     /**
611      * Clear the list of Views that have been created by this AppWidgetHost.
612      */
clearViews()613     protected void clearViews() {
614         synchronized (mListeners) {
615             mListeners.clear();
616         }
617     }
618 }
619 
620 
621