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