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