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 android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.Activity; 22 import android.compat.annotation.UnsupportedAppUsage; 23 import android.content.ActivityNotFoundException; 24 import android.content.Context; 25 import android.content.IntentSender; 26 import android.content.pm.PackageManager; 27 import android.os.Binder; 28 import android.os.Build; 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.Log; 39 import android.util.SparseArray; 40 import android.widget.RemoteViews; 41 import android.widget.RemoteViews.InteractionHandler; 42 43 import com.android.internal.R; 44 import com.android.internal.appwidget.IAppWidgetHost; 45 import com.android.internal.appwidget.IAppWidgetService; 46 47 import java.lang.ref.WeakReference; 48 import java.util.List; 49 50 /** 51 * AppWidgetHost provides the interaction with the AppWidget service for apps, 52 * like the home screen, that want to embed AppWidgets in their UI. 53 */ 54 public class AppWidgetHost { 55 56 private static final String TAG = "AppWidgetHost"; 57 58 static final int HANDLE_UPDATE = 1; 59 static final int HANDLE_PROVIDER_CHANGED = 2; 60 static final int HANDLE_PROVIDERS_CHANGED = 3; 61 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 62 static final int HANDLE_VIEW_DATA_CHANGED = 4; 63 static final int HANDLE_APP_WIDGET_REMOVED = 5; 64 static final int HANDLE_VIEW_UPDATE_DEFERRED = 6; 65 66 final static Object sServiceLock = new Object(); 67 @UnsupportedAppUsage 68 static IAppWidgetService sService; 69 static boolean sServiceInitialized = false; 70 private DisplayMetrics mDisplayMetrics; 71 72 private String mContextOpPackageName; 73 @UnsupportedAppUsage 74 private final Handler mHandler; 75 private final int mHostId; 76 private final Callbacks mCallbacks; 77 private final SparseArray<AppWidgetHostListener> mListeners = new SparseArray<>(); 78 private InteractionHandler mInteractionHandler; 79 80 static class Callbacks extends IAppWidgetHost.Stub { 81 private final WeakReference<Handler> mWeakHandler; 82 Callbacks(Handler handler)83 public Callbacks(Handler handler) { 84 mWeakHandler = new WeakReference<>(handler); 85 } 86 updateAppWidget(int appWidgetId, RemoteViews views)87 public void updateAppWidget(int appWidgetId, RemoteViews views) { 88 if (isLocalBinder() && views != null) { 89 views = views.clone(); 90 } 91 Handler handler = mWeakHandler.get(); 92 if (handler == null) { 93 return; 94 } 95 Message msg = handler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views); 96 msg.sendToTarget(); 97 } 98 providerChanged(int appWidgetId, AppWidgetProviderInfo info)99 public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) { 100 if (isLocalBinder() && info != null) { 101 info = info.clone(); 102 } 103 Handler handler = mWeakHandler.get(); 104 if (handler == null) { 105 return; 106 } 107 Message msg = handler.obtainMessage(HANDLE_PROVIDER_CHANGED, 108 appWidgetId, 0, info); 109 msg.sendToTarget(); 110 } 111 appWidgetRemoved(int appWidgetId)112 public void appWidgetRemoved(int appWidgetId) { 113 Handler handler = mWeakHandler.get(); 114 if (handler == null) { 115 return; 116 } 117 handler.obtainMessage(HANDLE_APP_WIDGET_REMOVED, appWidgetId, 0).sendToTarget(); 118 } 119 providersChanged()120 public void providersChanged() { 121 Handler handler = mWeakHandler.get(); 122 if (handler == null) { 123 return; 124 } 125 handler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget(); 126 } 127 viewDataChanged(int appWidgetId, int viewId)128 public void viewDataChanged(int appWidgetId, int viewId) { 129 Handler handler = mWeakHandler.get(); 130 if (handler == null) { 131 return; 132 } 133 Message msg = handler.obtainMessage(HANDLE_VIEW_DATA_CHANGED, 134 appWidgetId, viewId); 135 msg.sendToTarget(); 136 } 137 updateAppWidgetDeferred(int appWidgetId)138 public void updateAppWidgetDeferred(int appWidgetId) { 139 Handler handler = mWeakHandler.get(); 140 if (handler == null) { 141 return; 142 } 143 Message msg = handler.obtainMessage(HANDLE_VIEW_UPDATE_DEFERRED, appWidgetId, 0, null); 144 msg.sendToTarget(); 145 } 146 isLocalBinder()147 private static boolean isLocalBinder() { 148 return Process.myPid() == Binder.getCallingPid(); 149 } 150 } 151 152 class UpdateHandler extends Handler { UpdateHandler(Looper looper)153 public UpdateHandler(Looper looper) { 154 super(looper); 155 } 156 handleMessage(Message msg)157 public void handleMessage(Message msg) { 158 switch (msg.what) { 159 case HANDLE_UPDATE: { 160 updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj); 161 break; 162 } 163 case HANDLE_APP_WIDGET_REMOVED: { 164 dispatchOnAppWidgetRemoved(msg.arg1); 165 break; 166 } 167 case HANDLE_PROVIDER_CHANGED: { 168 onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj); 169 break; 170 } 171 case HANDLE_PROVIDERS_CHANGED: { 172 onProvidersChanged(); 173 break; 174 } 175 case HANDLE_VIEW_DATA_CHANGED: { 176 viewDataChanged(msg.arg1, msg.arg2); 177 break; 178 } 179 case HANDLE_VIEW_UPDATE_DEFERRED: { 180 updateAppWidgetDeferred(msg.arg1); 181 break; 182 } 183 } 184 } 185 } 186 AppWidgetHost(Context context, int hostId)187 public AppWidgetHost(Context context, int hostId) { 188 this(context, hostId, null, context.getMainLooper()); 189 } 190 191 @Nullable getListener(final int appWidgetId)192 private AppWidgetHostListener getListener(final int appWidgetId) { 193 AppWidgetHostListener tempListener = null; 194 synchronized (mListeners) { 195 tempListener = mListeners.get(appWidgetId); 196 } 197 return tempListener; 198 } 199 200 /** 201 * @hide 202 */ 203 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) AppWidgetHost(Context context, int hostId, InteractionHandler handler, Looper looper)204 public AppWidgetHost(Context context, int hostId, InteractionHandler handler, Looper looper) { 205 mContextOpPackageName = context.getOpPackageName(); 206 mHostId = hostId; 207 mInteractionHandler = handler; 208 mHandler = new UpdateHandler(looper); 209 mCallbacks = new Callbacks(mHandler); 210 mDisplayMetrics = context.getResources().getDisplayMetrics(); 211 bindService(context); 212 } 213 bindService(Context context)214 private static void bindService(Context context) { 215 synchronized (sServiceLock) { 216 if (sServiceInitialized) { 217 return; 218 } 219 sServiceInitialized = true; 220 PackageManager packageManager = context.getPackageManager(); 221 if (!packageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS) 222 && !context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) { 223 return; 224 } 225 IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE); 226 sService = IAppWidgetService.Stub.asInterface(b); 227 } 228 } 229 230 /** 231 * Start receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity 232 * becomes visible, i.e. from onStart() in your Activity. 233 */ startListening()234 public void startListening() { 235 if (sService == null) { 236 return; 237 } 238 final int[] idsToUpdate; 239 synchronized (mListeners) { 240 int n = mListeners.size(); 241 idsToUpdate = new int[n]; 242 for (int i = 0; i < n; i++) { 243 idsToUpdate[i] = mListeners.keyAt(i); 244 } 245 } 246 List<PendingHostUpdate> updates; 247 try { 248 updates = sService.startListening( 249 mCallbacks, mContextOpPackageName, mHostId, idsToUpdate).getList(); 250 } 251 catch (RemoteException e) { 252 throw new RuntimeException("system server dead?", e); 253 } 254 255 int N = updates.size(); 256 for (int i = 0; i < N; i++) { 257 PendingHostUpdate update = updates.get(i); 258 switch (update.type) { 259 case PendingHostUpdate.TYPE_VIEWS_UPDATE: 260 updateAppWidgetView(update.appWidgetId, update.views); 261 break; 262 case PendingHostUpdate.TYPE_PROVIDER_CHANGED: 263 onProviderChanged(update.appWidgetId, update.widgetInfo); 264 break; 265 case PendingHostUpdate.TYPE_VIEW_DATA_CHANGED: 266 viewDataChanged(update.appWidgetId, update.viewId); 267 break; 268 case PendingHostUpdate.TYPE_APP_WIDGET_REMOVED: 269 dispatchOnAppWidgetRemoved(update.appWidgetId); 270 break; 271 } 272 } 273 } 274 275 /** 276 * Stop receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity is 277 * no longer visible, i.e. from onStop() in your Activity. 278 */ stopListening()279 public void stopListening() { 280 if (sService == null) { 281 return; 282 } 283 try { 284 sService.stopListening(mContextOpPackageName, mHostId); 285 } 286 catch (RemoteException e) { 287 throw new RuntimeException("system server dead?", e); 288 } 289 } 290 291 /** 292 * Get a appWidgetId for a host in the calling process. 293 * 294 * @return a appWidgetId 295 */ allocateAppWidgetId()296 public int allocateAppWidgetId() { 297 if (sService == null) { 298 return -1; 299 } 300 try { 301 return sService.allocateAppWidgetId(mContextOpPackageName, mHostId); 302 } 303 catch (RemoteException e) { 304 throw new RuntimeException("system server dead?", e); 305 } 306 } 307 308 /** 309 * Starts an app widget provider configure activity for result on behalf of the caller. 310 * Use this method if the provider is in another profile as you are not allowed to start 311 * an activity in another profile. You can optionally provide a request code that is 312 * returned in {@link Activity#onActivityResult(int, int, android.content.Intent)} and 313 * an options bundle to be passed to the started activity. 314 * <p> 315 * Note that the provided app widget has to be bound for this method to work. 316 * </p> 317 * 318 * @param activity The activity from which to start the configure one. 319 * @param appWidgetId The bound app widget whose provider's config activity to start. 320 * @param requestCode Optional request code retuned with the result. 321 * @param intentFlags Optional intent flags. 322 * 323 * @throws android.content.ActivityNotFoundException If the activity is not found. 324 * 325 * @see AppWidgetProviderInfo#getProfile() 326 */ startAppWidgetConfigureActivityForResult(@onNull Activity activity, int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options)327 public final void startAppWidgetConfigureActivityForResult(@NonNull Activity activity, 328 int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options) { 329 if (sService == null) { 330 return; 331 } 332 try { 333 IntentSender intentSender = sService.createAppWidgetConfigIntentSender( 334 mContextOpPackageName, appWidgetId, intentFlags); 335 if (intentSender != null) { 336 activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0, 337 options); 338 } else { 339 throw new ActivityNotFoundException(); 340 } 341 } catch (IntentSender.SendIntentException e) { 342 throw new ActivityNotFoundException(); 343 } catch (RemoteException e) { 344 throw new RuntimeException("system server dead?", e); 345 } 346 } 347 348 /** 349 * Set the visibiity of all widgets associated with this host to hidden 350 * 351 * @hide 352 */ setAppWidgetHidden()353 public void setAppWidgetHidden() { 354 if (sService == null) { 355 return; 356 } 357 try { 358 sService.setAppWidgetHidden(mContextOpPackageName, mHostId); 359 } catch (RemoteException e) { 360 throw new RuntimeException("System server dead?", e); 361 } 362 } 363 364 /** 365 * Set the host's interaction handler. 366 * 367 * @hide 368 */ setInteractionHandler(InteractionHandler interactionHandler)369 public void setInteractionHandler(InteractionHandler interactionHandler) { 370 mInteractionHandler = interactionHandler; 371 } 372 373 /** 374 * Gets a list of all the appWidgetIds that are bound to the current host 375 */ getAppWidgetIds()376 public int[] getAppWidgetIds() { 377 if (sService == null) { 378 return new int[0]; 379 } 380 try { 381 return sService.getAppWidgetIdsForHost(mContextOpPackageName, mHostId); 382 } catch (RemoteException e) { 383 throw new RuntimeException("system server dead?", e); 384 } 385 } 386 387 /** 388 * Stop listening to changes for this AppWidget. 389 */ deleteAppWidgetId(int appWidgetId)390 public void deleteAppWidgetId(int appWidgetId) { 391 if (sService == null) { 392 return; 393 } 394 removeListener(appWidgetId); 395 try { 396 sService.deleteAppWidgetId(mContextOpPackageName, appWidgetId); 397 } catch (RemoteException e) { 398 throw new RuntimeException("system server dead?", e); 399 } 400 } 401 402 /** 403 * Remove all records about this host from the AppWidget manager. 404 * <ul> 405 * <li>Call this when initializing your database, as it might be because of a data wipe.</li> 406 * <li>Call this to have the AppWidget manager release all resources associated with your 407 * host. Any future calls about this host will cause the records to be re-allocated.</li> 408 * </ul> 409 */ deleteHost()410 public void deleteHost() { 411 if (sService == null) { 412 return; 413 } 414 try { 415 sService.deleteHost(mContextOpPackageName, mHostId); 416 } 417 catch (RemoteException e) { 418 throw new RuntimeException("system server dead?", e); 419 } 420 } 421 422 /** 423 * Remove all records about all hosts for your package. 424 * <ul> 425 * <li>Call this when initializing your database, as it might be because of a data wipe.</li> 426 * <li>Call this to have the AppWidget manager release all resources associated with your 427 * host. Any future calls about this host will cause the records to be re-allocated.</li> 428 * </ul> 429 */ deleteAllHosts()430 public static void deleteAllHosts() { 431 if (sService == null) { 432 return; 433 } 434 try { 435 sService.deleteAllHosts(); 436 } 437 catch (RemoteException e) { 438 throw new RuntimeException("system server dead?", e); 439 } 440 } 441 442 /** 443 * Create the AppWidgetHostView for the given widget. 444 * The AppWidgetHost retains a pointer to the newly-created View. 445 */ createView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget)446 public final AppWidgetHostView createView(Context context, int appWidgetId, 447 AppWidgetProviderInfo appWidget) { 448 if (sService == null) { 449 return null; 450 } 451 AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget); 452 view.setInteractionHandler(mInteractionHandler); 453 view.setAppWidget(appWidgetId, appWidget); 454 setListener(appWidgetId, view); 455 456 return view; 457 } 458 459 /** 460 * Called to create the AppWidgetHostView. Override to return a custom subclass if you 461 * need it. {@more} 462 */ onCreateView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget)463 protected AppWidgetHostView onCreateView(Context context, int appWidgetId, 464 AppWidgetProviderInfo appWidget) { 465 return new AppWidgetHostView(context, mInteractionHandler); 466 } 467 468 /** 469 * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk. 470 */ onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget)471 protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) { 472 AppWidgetHostListener v = getListener(appWidgetId); 473 474 // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the 475 // AppWidgetService, which doesn't have our context, hence we need to do the 476 // conversion here. 477 appWidget.updateDimensions(mDisplayMetrics); 478 if (v != null) { 479 v.onUpdateProviderInfo(appWidget); 480 } 481 } 482 483 /** 484 * This interface specifies the actions to be performed on the app widget based on the calls 485 * from the service 486 * 487 * @hide 488 */ 489 public interface AppWidgetHostListener { 490 491 /** 492 * This function is called when the service want to reset the app widget provider info 493 * @param appWidget The new app widget provider info 494 * 495 * @hide 496 */ onUpdateProviderInfo(@ullable AppWidgetProviderInfo appWidget)497 void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo appWidget); 498 499 /** 500 * This function is called when the {@code RemoteViews} of the app widget is updated 501 * @param views The new {@code RemoteViews} to be set for the app widget 502 * 503 * @hide 504 */ updateAppWidget(@ullable RemoteViews views)505 void updateAppWidget(@Nullable RemoteViews views); 506 507 /** 508 * Called for the listener to handle deferred {@code RemoteViews} updates. Default 509 * implementation is to update the widget directly. 510 * @param packageName The package name used for uid verification on the service side 511 * @param appWidgetId The widget id of the listener 512 * 513 * @hide 514 */ updateAppWidgetDeferred(String packageName, int appWidgetId)515 default void updateAppWidgetDeferred(String packageName, int appWidgetId) { 516 RemoteViews latestViews = null; 517 try { 518 latestViews = sService.getAppWidgetViews(packageName, appWidgetId); 519 } catch (Exception e) { 520 Log.e(TAG, "updateAppWidgetDeferred: ", e); 521 } 522 updateAppWidget(latestViews); 523 } 524 525 /** 526 * This function is called when the view ID is changed for the app widget 527 * @param viewId The new view ID to be be set for the widget 528 * 529 * @hide 530 */ onViewDataChanged(int viewId)531 void onViewDataChanged(int viewId); 532 } 533 dispatchOnAppWidgetRemoved(int appWidgetId)534 void dispatchOnAppWidgetRemoved(int appWidgetId) { 535 removeListener(appWidgetId); 536 onAppWidgetRemoved(appWidgetId); 537 } 538 539 /** 540 * Called when the app widget is removed for appWidgetId 541 * @param appWidgetId 542 */ onAppWidgetRemoved(int appWidgetId)543 public void onAppWidgetRemoved(int appWidgetId) { 544 // Does nothing 545 } 546 547 /** 548 * Called when the set of available widgets changes (ie. widget containing packages 549 * are added, updated or removed, or widget components are enabled or disabled.) 550 */ onProvidersChanged()551 protected void onProvidersChanged() { 552 // Does nothing 553 } 554 555 /** 556 * Create an AppWidgetHostListener for the given widget. 557 * The AppWidgetHost retains a pointer to the newly-created listener. 558 * @param appWidgetId The ID of the app widget for which to add the listener 559 * @param listener The listener interface that deals with actions towards the widget view 560 * @hide 561 */ setListener(int appWidgetId, @NonNull AppWidgetHostListener listener)562 public void setListener(int appWidgetId, @NonNull AppWidgetHostListener listener) { 563 synchronized (mListeners) { 564 mListeners.put(appWidgetId, listener); 565 } 566 RemoteViews views = null; 567 try { 568 views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId); 569 } catch (RemoteException e) { 570 throw new RuntimeException("system server dead?", e); 571 } 572 listener.updateAppWidget(views); 573 } 574 575 /** 576 * Delete the listener for the given widget 577 * @param appWidgetId The ID of the app widget for which the listener is to be deleted 578 579 * @hide 580 */ removeListener(int appWidgetId)581 public void removeListener(int appWidgetId) { 582 synchronized (mListeners) { 583 mListeners.remove(appWidgetId); 584 } 585 } 586 updateAppWidgetView(int appWidgetId, RemoteViews views)587 void updateAppWidgetView(int appWidgetId, RemoteViews views) { 588 AppWidgetHostListener v = getListener(appWidgetId); 589 if (v != null) { 590 v.updateAppWidget(views); 591 } 592 } 593 viewDataChanged(int appWidgetId, int viewId)594 void viewDataChanged(int appWidgetId, int viewId) { 595 AppWidgetHostListener v = getListener(appWidgetId); 596 if (v != null) { 597 v.onViewDataChanged(viewId); 598 } 599 } 600 updateAppWidgetDeferred(int appWidgetId)601 private void updateAppWidgetDeferred(int appWidgetId) { 602 AppWidgetHostListener v = getListener(appWidgetId); 603 if (v == null) { 604 Log.e(TAG, "updateAppWidgetDeferred: null listener for id: " + appWidgetId); 605 return; 606 } 607 v.updateAppWidgetDeferred(mContextOpPackageName, appWidgetId); 608 } 609 610 /** 611 * Clear the list of Views that have been created by this AppWidgetHost. 612 */ clearViews()613 protected void clearViews() { 614 synchronized (mListeners) { 615 mListeners.clear(); 616 } 617 } 618 } 619 620 621