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