AppWidgetHost.java revision c71c42fdb2ee54a419dc8eb0a5f4f82532b16c0c
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.util.DisplayMetrics; 36import android.util.TypedValue; 37import android.widget.RemoteViews; 38import android.widget.RemoteViews.OnClickHandler; 39 40import com.android.internal.appwidget.IAppWidgetHost; 41import com.android.internal.appwidget.IAppWidgetService; 42 43/** 44 * AppWidgetHost provides the interaction with the AppWidget service for apps, 45 * like the home screen, that want to embed AppWidgets in their UI. 46 */ 47public class AppWidgetHost { 48 49 static final int HANDLE_UPDATE = 1; 50 static final int HANDLE_PROVIDER_CHANGED = 2; 51 static final int HANDLE_PROVIDERS_CHANGED = 3; 52 static final int HANDLE_VIEW_DATA_CHANGED = 4; 53 54 final static Object sServiceLock = new Object(); 55 static IAppWidgetService sService; 56 private DisplayMetrics mDisplayMetrics; 57 58 Context mContext; 59 Handler mHandler; 60 int mHostId; 61 Callbacks mCallbacks = new Callbacks(); 62 final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<Integer, AppWidgetHostView>(); 63 private OnClickHandler mOnClickHandler; 64 65 class Callbacks extends IAppWidgetHost.Stub { 66 public void updateAppWidget(int appWidgetId, RemoteViews views) { 67 if (isLocalBinder() && views != null) { 68 views = views.clone(); 69 } 70 Message msg = mHandler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views); 71 msg.sendToTarget(); 72 } 73 74 public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) { 75 if (isLocalBinder() && info != null) { 76 info = info.clone(); 77 } 78 Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED, 79 appWidgetId, 0, info); 80 msg.sendToTarget(); 81 } 82 83 public void providersChanged() { 84 mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget(); 85 } 86 87 public void viewDataChanged(int appWidgetId, int viewId) { 88 Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED, 89 appWidgetId, viewId); 90 msg.sendToTarget(); 91 } 92 } 93 94 class UpdateHandler extends Handler { 95 public UpdateHandler(Looper looper) { 96 super(looper); 97 } 98 99 public void handleMessage(Message msg) { 100 switch (msg.what) { 101 case HANDLE_UPDATE: { 102 updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj); 103 break; 104 } 105 case HANDLE_PROVIDER_CHANGED: { 106 onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj); 107 break; 108 } 109 case HANDLE_PROVIDERS_CHANGED: { 110 onProvidersChanged(); 111 break; 112 } 113 case HANDLE_VIEW_DATA_CHANGED: { 114 viewDataChanged(msg.arg1, msg.arg2); 115 break; 116 } 117 } 118 } 119 } 120 121 public AppWidgetHost(Context context, int hostId) { 122 this(context, hostId, null, context.getMainLooper()); 123 } 124 125 /** 126 * @hide 127 */ 128 public AppWidgetHost(Context context, int hostId, OnClickHandler handler, Looper looper) { 129 mContext = context; 130 mHostId = hostId; 131 mOnClickHandler = handler; 132 mHandler = new UpdateHandler(looper); 133 mDisplayMetrics = context.getResources().getDisplayMetrics(); 134 bindService(); 135 } 136 137 138 private static void bindService() { 139 synchronized (sServiceLock) { 140 if (sService == null) { 141 IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE); 142 sService = IAppWidgetService.Stub.asInterface(b); 143 } 144 } 145 } 146 147 /** 148 * Start receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity 149 * becomes visible, i.e. from onStart() in your Activity. 150 */ 151 public void startListening() { 152 int[] updatedIds; 153 ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>(); 154 try { 155 updatedIds = sService.startListening(mCallbacks, mContext.getPackageName(), mHostId, 156 updatedViews); 157 } 158 catch (RemoteException e) { 159 throw new RuntimeException("system server dead?", e); 160 } 161 162 final int N = updatedIds.length; 163 for (int i = 0; i < N; i++) { 164 updateAppWidgetView(updatedIds[i], updatedViews.get(i)); 165 } 166 } 167 168 /** 169 * Stop receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity is 170 * no longer visible, i.e. from onStop() in your Activity. 171 */ 172 public void stopListening() { 173 try { 174 sService.stopListening(mContext.getPackageName(), mHostId); 175 } 176 catch (RemoteException e) { 177 throw new RuntimeException("system server dead?", e); 178 } 179 180 // This is here because keyguard needs it since it'll be switching users after this call. 181 // If it turns out other apps need to call this often, we should re-think how this works. 182 clearViews(); 183 } 184 185 /** 186 * Get a appWidgetId for a host in the calling process. 187 * 188 * @return a appWidgetId 189 */ 190 public int allocateAppWidgetId() { 191 try { 192 return sService.allocateAppWidgetId(mContext.getPackageName(), mHostId); 193 } 194 catch (RemoteException e) { 195 throw new RuntimeException("system server dead?", e); 196 } 197 } 198 199 /** 200 * Starts an app widget provider configure activity for result on behalf of the caller. 201 * Use this method if the provider is in another profile as you are not allowed to start 202 * an activity in another profile. The provided intent must have action {@link 203 * AppWidgetManager#ACTION_APPWIDGET_CONFIGURE}. The widget id must be specified by 204 * the {@link AppWidgetManager#EXTRA_APPWIDGET_ID} extra. The provider configure 205 * activity must be specified as the component name of the intent via {@link 206 * Intent#setComponent(android.content.ComponentName)}. The user profile under which 207 * the provider operates is specified via the {@link 208 * AppWidgetManager#EXTRA_APPWIDGET_PROVIDER_PROFILE} extra. If a user profile is 209 * not provided, the current user is used. Finally, you can optionally provide a 210 * request code that is returned in {@link Activity#onActivityResult(int, int, 211 * android.content.Intent)}. 212 * 213 * @param activity The activity from which to start the configure one. 214 * @param intent Properly populated intent for launching the configuration activity. 215 * @param requestCode Optional request code retuned with the result. 216 * 217 * @throws android.content.ActivityNotFoundException If the activity is not found. 218 * 219 * @see AppWidgetProviderInfo#getProfile() 220 */ 221 public final void startAppWidgetConfigureActivityForResult(Activity activity, Intent intent, 222 int requestCode) { 223 try { 224 IntentSender intentSender = sService.createAppWidgetConfigIntentSender( 225 mContext.getPackageName(), intent); 226 if (intentSender != null) { 227 activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0); 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 */ 243 public int[] getAppWidgetIds() { 244 try { 245 if (sService == null) { 246 bindService(); 247 } 248 return sService.getAppWidgetIdsForHost(mContext.getPackageName(), mHostId); 249 } catch (RemoteException e) { 250 throw new RuntimeException("system server dead?", e); 251 } 252 } 253 254 private boolean isLocalBinder() { 255 return Process.myPid() == Binder.getCallingPid(); 256 } 257 258 /** 259 * Stop listening to changes for this AppWidget. 260 */ 261 public void deleteAppWidgetId(int appWidgetId) { 262 synchronized (mViews) { 263 mViews.remove(appWidgetId); 264 try { 265 sService.deleteAppWidgetId(mContext.getPackageName(), 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 */ 281 public void deleteHost() { 282 try { 283 sService.deleteHost(mContext.getPackageName(), 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 */ 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 */ 311 public final AppWidgetHostView createView(Context context, int appWidgetId, 312 AppWidgetProviderInfo appWidget) { 313 AppWidgetHostView view = onCreateView(mContext, 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(mContext.getPackageName(), 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 */ 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 */ 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 */ 369 protected void onProvidersChanged() { 370 // Does nothing 371 } 372 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 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 */ 396 protected void clearViews() { 397 mViews.clear(); 398 } 399} 400 401 402