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