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