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