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<AppWidgetHostView> mViews = 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     /**
175      * @hide
176      */
177     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
AppWidgetHost(Context context, int hostId, InteractionHandler handler, Looper looper)178     public AppWidgetHost(Context context, int hostId, InteractionHandler handler, Looper looper) {
179         mContextOpPackageName = context.getOpPackageName();
180         mHostId = hostId;
181         mInteractionHandler = handler;
182         mHandler = new UpdateHandler(looper);
183         mCallbacks = new Callbacks(mHandler);
184         mDisplayMetrics = context.getResources().getDisplayMetrics();
185         bindService(context);
186     }
187 
bindService(Context context)188     private static void bindService(Context context) {
189         synchronized (sServiceLock) {
190             if (sServiceInitialized) {
191                 return;
192             }
193             sServiceInitialized = true;
194             PackageManager packageManager = context.getPackageManager();
195             if (!packageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
196                     && !context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
197                 return;
198             }
199             IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
200             sService = IAppWidgetService.Stub.asInterface(b);
201         }
202     }
203 
204     /**
205      * Start receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity
206      * becomes visible, i.e. from onStart() in your Activity.
207      */
startListening()208     public void startListening() {
209         if (sService == null) {
210             return;
211         }
212         final int[] idsToUpdate;
213         synchronized (mViews) {
214             int N = mViews.size();
215             idsToUpdate = new int[N];
216             for (int i = 0; i < N; i++) {
217                 idsToUpdate[i] = mViews.keyAt(i);
218             }
219         }
220         List<PendingHostUpdate> updates;
221         try {
222             updates = sService.startListening(
223                     mCallbacks, mContextOpPackageName, mHostId, idsToUpdate).getList();
224         }
225         catch (RemoteException e) {
226             throw new RuntimeException("system server dead?", e);
227         }
228 
229         int N = updates.size();
230         for (int i = 0; i < N; i++) {
231             PendingHostUpdate update = updates.get(i);
232             switch (update.type) {
233                 case PendingHostUpdate.TYPE_VIEWS_UPDATE:
234                     updateAppWidgetView(update.appWidgetId, update.views);
235                     break;
236                 case PendingHostUpdate.TYPE_PROVIDER_CHANGED:
237                     onProviderChanged(update.appWidgetId, update.widgetInfo);
238                     break;
239                 case PendingHostUpdate.TYPE_VIEW_DATA_CHANGED:
240                     viewDataChanged(update.appWidgetId, update.viewId);
241                     break;
242                 case PendingHostUpdate.TYPE_APP_WIDGET_REMOVED:
243                     dispatchOnAppWidgetRemoved(update.appWidgetId);
244                     break;
245             }
246         }
247     }
248 
249     /**
250      * Stop receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity is
251      * no longer visible, i.e. from onStop() in your Activity.
252      */
stopListening()253     public void stopListening() {
254         if (sService == null) {
255             return;
256         }
257         try {
258             sService.stopListening(mContextOpPackageName, mHostId);
259         }
260         catch (RemoteException e) {
261             throw new RuntimeException("system server dead?", e);
262         }
263     }
264 
265     /**
266      * Get a appWidgetId for a host in the calling process.
267      *
268      * @return a appWidgetId
269      */
allocateAppWidgetId()270     public int allocateAppWidgetId() {
271         if (sService == null) {
272             return -1;
273         }
274         try {
275             return sService.allocateAppWidgetId(mContextOpPackageName, mHostId);
276         }
277         catch (RemoteException e) {
278             throw new RuntimeException("system server dead?", e);
279         }
280     }
281 
282     /**
283      * Starts an app widget provider configure activity for result on behalf of the caller.
284      * Use this method if the provider is in another profile as you are not allowed to start
285      * an activity in another profile. You can optionally provide a request code that is
286      * returned in {@link Activity#onActivityResult(int, int, android.content.Intent)} and
287      * an options bundle to be passed to the started activity.
288      * <p>
289      * Note that the provided app widget has to be bound for this method to work.
290      * </p>
291      *
292      * @param activity The activity from which to start the configure one.
293      * @param appWidgetId The bound app widget whose provider's config activity to start.
294      * @param requestCode Optional request code retuned with the result.
295      * @param intentFlags Optional intent flags.
296      *
297      * @throws android.content.ActivityNotFoundException If the activity is not found.
298      *
299      * @see AppWidgetProviderInfo#getProfile()
300      */
startAppWidgetConfigureActivityForResult(@onNull Activity activity, int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options)301     public final void startAppWidgetConfigureActivityForResult(@NonNull Activity activity,
302             int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options) {
303         if (sService == null) {
304             return;
305         }
306         try {
307             IntentSender intentSender = sService.createAppWidgetConfigIntentSender(
308                     mContextOpPackageName, appWidgetId, intentFlags);
309             if (intentSender != null) {
310                 activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0,
311                         options);
312             } else {
313                 throw new ActivityNotFoundException();
314             }
315         } catch (IntentSender.SendIntentException e) {
316             throw new ActivityNotFoundException();
317         } catch (RemoteException e) {
318             throw new RuntimeException("system server dead?", e);
319         }
320     }
321 
322     /**
323      * Set the host's interaction handler.
324      *
325      * @hide
326      */
setInteractionHandler(InteractionHandler interactionHandler)327     public void setInteractionHandler(InteractionHandler interactionHandler) {
328         mInteractionHandler = interactionHandler;
329     }
330 
331     /**
332      * Gets a list of all the appWidgetIds that are bound to the current host
333      */
getAppWidgetIds()334     public int[] getAppWidgetIds() {
335         if (sService == null) {
336             return new int[0];
337         }
338         try {
339             return sService.getAppWidgetIdsForHost(mContextOpPackageName, mHostId);
340         } catch (RemoteException e) {
341             throw new RuntimeException("system server dead?", e);
342         }
343     }
344 
345     /**
346      * Stop listening to changes for this AppWidget.
347      */
deleteAppWidgetId(int appWidgetId)348     public void deleteAppWidgetId(int appWidgetId) {
349         if (sService == null) {
350             return;
351         }
352         synchronized (mViews) {
353             mViews.remove(appWidgetId);
354             try {
355                 sService.deleteAppWidgetId(mContextOpPackageName, appWidgetId);
356             }
357             catch (RemoteException e) {
358                 throw new RuntimeException("system server dead?", e);
359             }
360         }
361     }
362 
363     /**
364      * Remove all records about this host from the AppWidget manager.
365      * <ul>
366      *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
367      *   <li>Call this to have the AppWidget manager release all resources associated with your
368      *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
369      * </ul>
370      */
deleteHost()371     public void deleteHost() {
372         if (sService == null) {
373             return;
374         }
375         try {
376             sService.deleteHost(mContextOpPackageName, mHostId);
377         }
378         catch (RemoteException e) {
379             throw new RuntimeException("system server dead?", e);
380         }
381     }
382 
383     /**
384      * Remove all records about all hosts for your package.
385      * <ul>
386      *   <li>Call this when initializing your database, as it might be because of a data wipe.</li>
387      *   <li>Call this to have the AppWidget manager release all resources associated with your
388      *   host.  Any future calls about this host will cause the records to be re-allocated.</li>
389      * </ul>
390      */
deleteAllHosts()391     public static void deleteAllHosts() {
392         if (sService == null) {
393             return;
394         }
395         try {
396             sService.deleteAllHosts();
397         }
398         catch (RemoteException e) {
399             throw new RuntimeException("system server dead?", e);
400         }
401     }
402 
403     /**
404      * Create the AppWidgetHostView for the given widget.
405      * The AppWidgetHost retains a pointer to the newly-created View.
406      */
createView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget)407     public final AppWidgetHostView createView(Context context, int appWidgetId,
408             AppWidgetProviderInfo appWidget) {
409         if (sService == null) {
410             return null;
411         }
412         AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
413         view.setInteractionHandler(mInteractionHandler);
414         view.setAppWidget(appWidgetId, appWidget);
415         synchronized (mViews) {
416             mViews.put(appWidgetId, view);
417         }
418         RemoteViews views;
419         try {
420             views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
421         } catch (RemoteException e) {
422             throw new RuntimeException("system server dead?", e);
423         }
424         view.updateAppWidget(views);
425 
426         return view;
427     }
428 
429     /**
430      * Called to create the AppWidgetHostView.  Override to return a custom subclass if you
431      * need it.  {@more}
432      */
onCreateView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget)433     protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
434             AppWidgetProviderInfo appWidget) {
435         return new AppWidgetHostView(context, mInteractionHandler);
436     }
437 
438     /**
439      * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
440      */
onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget)441     protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
442         AppWidgetHostView v;
443 
444         // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the
445         // AppWidgetService, which doesn't have our context, hence we need to do the
446         // conversion here.
447         appWidget.updateDimensions(mDisplayMetrics);
448         synchronized (mViews) {
449             v = mViews.get(appWidgetId);
450         }
451         if (v != null) {
452             v.resetAppWidget(appWidget);
453         }
454     }
455 
dispatchOnAppWidgetRemoved(int appWidgetId)456     void dispatchOnAppWidgetRemoved(int appWidgetId) {
457         synchronized (mViews) {
458             mViews.remove(appWidgetId);
459         }
460         onAppWidgetRemoved(appWidgetId);
461     }
462 
463     /**
464      * Called when the app widget is removed for appWidgetId
465      * @param appWidgetId
466      */
onAppWidgetRemoved(int appWidgetId)467     public void onAppWidgetRemoved(int appWidgetId) {
468         // Does nothing
469     }
470 
471     /**
472      * Called when the set of available widgets changes (ie. widget containing packages
473      * are added, updated or removed, or widget components are enabled or disabled.)
474      */
onProvidersChanged()475     protected void onProvidersChanged() {
476         // Does nothing
477     }
478 
updateAppWidgetView(int appWidgetId, RemoteViews views)479     void updateAppWidgetView(int appWidgetId, RemoteViews views) {
480         AppWidgetHostView v;
481         synchronized (mViews) {
482             v = mViews.get(appWidgetId);
483         }
484         if (v != null) {
485             v.updateAppWidget(views);
486         }
487     }
488 
viewDataChanged(int appWidgetId, int viewId)489     void viewDataChanged(int appWidgetId, int viewId) {
490         AppWidgetHostView v;
491         synchronized (mViews) {
492             v = mViews.get(appWidgetId);
493         }
494         if (v != null) {
495             v.viewDataChanged(viewId);
496         }
497     }
498 
499     /**
500      * Clear the list of Views that have been created by this AppWidgetHost.
501      */
clearViews()502     protected void clearViews() {
503         synchronized (mViews) {
504             mViews.clear();
505         }
506     }
507 }
508 
509 
510