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 * Returns an {@link IntentSender} for starting the configuration activity for the widget. 310 * 311 * @return The {@link IntentSender} or null if service is currently unavailable 312 * 313 * @throws android.content.ActivityNotFoundException If configuration activity is not found. 314 * 315 * @see #startAppWidgetConfigureActivityForResult 316 * 317 * @hide 318 */ 319 @Nullable getIntentSenderForConfigureActivity(int appWidgetId, int intentFlags)320 public final IntentSender getIntentSenderForConfigureActivity(int appWidgetId, 321 int intentFlags) { 322 if (sService == null) { 323 return null; 324 } 325 326 IntentSender intentSender; 327 try { 328 intentSender = sService.createAppWidgetConfigIntentSender(mContextOpPackageName, 329 appWidgetId, intentFlags); 330 } catch (RemoteException e) { 331 throw new RuntimeException("system server dead?", e); 332 } 333 334 if (intentSender == null) { 335 throw new ActivityNotFoundException(); 336 } 337 return intentSender; 338 } 339 340 /** 341 * Starts an app widget provider configure activity for result on behalf of the caller. 342 * Use this method if the provider is in another profile as you are not allowed to start 343 * an activity in another profile. You can optionally provide a request code that is 344 * returned in {@link Activity#onActivityResult(int, int, android.content.Intent)} and 345 * an options bundle to be passed to the started activity. 346 * <p> 347 * Note that the provided app widget has to be bound for this method to work. 348 * </p> 349 * 350 * @param activity The activity from which to start the configure one. 351 * @param appWidgetId The bound app widget whose provider's config activity to start. 352 * @param requestCode Optional request code retuned with the result. 353 * @param intentFlags Optional intent flags. 354 * 355 * @throws android.content.ActivityNotFoundException If the activity is not found. 356 * 357 * @see AppWidgetProviderInfo#getProfile() 358 */ startAppWidgetConfigureActivityForResult(@onNull Activity activity, int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options)359 public final void startAppWidgetConfigureActivityForResult(@NonNull Activity activity, 360 int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options) { 361 if (sService == null) { 362 return; 363 } 364 try { 365 IntentSender intentSender = getIntentSenderForConfigureActivity(appWidgetId, 366 intentFlags); 367 activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0, options); 368 } catch (IntentSender.SendIntentException e) { 369 throw new ActivityNotFoundException(); 370 } 371 } 372 373 /** 374 * Set the visibiity of all widgets associated with this host to hidden 375 * 376 * @hide 377 */ setAppWidgetHidden()378 public void setAppWidgetHidden() { 379 if (sService == null) { 380 return; 381 } 382 try { 383 sService.setAppWidgetHidden(mContextOpPackageName, mHostId); 384 } catch (RemoteException e) { 385 throw new RuntimeException("System server dead?", e); 386 } 387 } 388 389 /** 390 * Set the host's interaction handler. 391 * 392 * @hide 393 */ setInteractionHandler(InteractionHandler interactionHandler)394 public void setInteractionHandler(InteractionHandler interactionHandler) { 395 mInteractionHandler = interactionHandler; 396 } 397 398 /** 399 * Gets a list of all the appWidgetIds that are bound to the current host 400 */ getAppWidgetIds()401 public int[] getAppWidgetIds() { 402 if (sService == null) { 403 return new int[0]; 404 } 405 try { 406 return sService.getAppWidgetIdsForHost(mContextOpPackageName, mHostId); 407 } catch (RemoteException e) { 408 throw new RuntimeException("system server dead?", e); 409 } 410 } 411 412 /** 413 * Stop listening to changes for this AppWidget. 414 */ deleteAppWidgetId(int appWidgetId)415 public void deleteAppWidgetId(int appWidgetId) { 416 if (sService == null) { 417 return; 418 } 419 removeListener(appWidgetId); 420 try { 421 sService.deleteAppWidgetId(mContextOpPackageName, appWidgetId); 422 } catch (RemoteException e) { 423 throw new RuntimeException("system server dead?", e); 424 } 425 } 426 427 /** 428 * Remove all records about this host from the AppWidget manager. 429 * <ul> 430 * <li>Call this when initializing your database, as it might be because of a data wipe.</li> 431 * <li>Call this to have the AppWidget manager release all resources associated with your 432 * host. Any future calls about this host will cause the records to be re-allocated.</li> 433 * </ul> 434 */ deleteHost()435 public void deleteHost() { 436 if (sService == null) { 437 return; 438 } 439 try { 440 sService.deleteHost(mContextOpPackageName, mHostId); 441 } 442 catch (RemoteException e) { 443 throw new RuntimeException("system server dead?", e); 444 } 445 } 446 447 /** 448 * Remove all records about all hosts for your package. 449 * <ul> 450 * <li>Call this when initializing your database, as it might be because of a data wipe.</li> 451 * <li>Call this to have the AppWidget manager release all resources associated with your 452 * host. Any future calls about this host will cause the records to be re-allocated.</li> 453 * </ul> 454 */ deleteAllHosts()455 public static void deleteAllHosts() { 456 if (sService == null) { 457 return; 458 } 459 try { 460 sService.deleteAllHosts(); 461 } 462 catch (RemoteException e) { 463 throw new RuntimeException("system server dead?", e); 464 } 465 } 466 467 /** 468 * Create the AppWidgetHostView for the given widget. 469 * The AppWidgetHost retains a pointer to the newly-created View. 470 */ createView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget)471 public final AppWidgetHostView createView(Context context, int appWidgetId, 472 AppWidgetProviderInfo appWidget) { 473 if (sService == null) { 474 return null; 475 } 476 AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget); 477 view.setInteractionHandler(mInteractionHandler); 478 view.setAppWidget(appWidgetId, appWidget); 479 setListener(appWidgetId, view); 480 481 return view; 482 } 483 484 /** 485 * Called to create the AppWidgetHostView. Override to return a custom subclass if you 486 * need it. {@more} 487 */ onCreateView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget)488 protected AppWidgetHostView onCreateView(Context context, int appWidgetId, 489 AppWidgetProviderInfo appWidget) { 490 return new AppWidgetHostView(context, mInteractionHandler); 491 } 492 493 /** 494 * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk. 495 */ onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget)496 protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) { 497 AppWidgetHostListener v = getListener(appWidgetId); 498 499 // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the 500 // AppWidgetService, which doesn't have our context, hence we need to do the 501 // conversion here. 502 appWidget.updateDimensions(mDisplayMetrics); 503 if (v != null) { 504 v.onUpdateProviderInfo(appWidget); 505 } 506 } 507 508 /** 509 * This interface specifies the actions to be performed on the app widget based on the calls 510 * from the service 511 * 512 * @hide 513 */ 514 public interface AppWidgetHostListener { 515 516 /** 517 * This function is called when the service want to reset the app widget provider info 518 * @param appWidget The new app widget provider info 519 * 520 * @hide 521 */ onUpdateProviderInfo(@ullable AppWidgetProviderInfo appWidget)522 void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo appWidget); 523 524 /** 525 * This function is called when the {@code RemoteViews} of the app widget is updated 526 * @param views The new {@code RemoteViews} to be set for the app widget 527 * 528 * @hide 529 */ updateAppWidget(@ullable RemoteViews views)530 void updateAppWidget(@Nullable RemoteViews views); 531 532 /** 533 * Called for the listener to handle deferred {@code RemoteViews} updates. Default 534 * implementation is to update the widget directly. 535 * @param packageName The package name used for uid verification on the service side 536 * @param appWidgetId The widget id of the listener 537 * 538 * @hide 539 */ updateAppWidgetDeferred(String packageName, int appWidgetId)540 default void updateAppWidgetDeferred(String packageName, int appWidgetId) { 541 RemoteViews latestViews = null; 542 try { 543 latestViews = sService.getAppWidgetViews(packageName, appWidgetId); 544 } catch (Exception e) { 545 Log.e(TAG, "updateAppWidgetDeferred: ", e); 546 } 547 updateAppWidget(latestViews); 548 } 549 550 /** 551 * This function is called when the view ID is changed for the app widget 552 * @param viewId The new view ID to be be set for the widget 553 * 554 * @hide 555 */ onViewDataChanged(int viewId)556 void onViewDataChanged(int viewId); 557 } 558 dispatchOnAppWidgetRemoved(int appWidgetId)559 void dispatchOnAppWidgetRemoved(int appWidgetId) { 560 removeListener(appWidgetId); 561 onAppWidgetRemoved(appWidgetId); 562 } 563 564 /** 565 * Called when the app widget is removed for appWidgetId 566 * @param appWidgetId 567 */ onAppWidgetRemoved(int appWidgetId)568 public void onAppWidgetRemoved(int appWidgetId) { 569 // Does nothing 570 } 571 572 /** 573 * Called when the set of available widgets changes (ie. widget containing packages 574 * are added, updated or removed, or widget components are enabled or disabled.) 575 */ onProvidersChanged()576 protected void onProvidersChanged() { 577 // Does nothing 578 } 579 580 /** 581 * Create an AppWidgetHostListener for the given widget. 582 * The AppWidgetHost retains a pointer to the newly-created listener. 583 * @param appWidgetId The ID of the app widget for which to add the listener 584 * @param listener The listener interface that deals with actions towards the widget view 585 * @hide 586 */ setListener(int appWidgetId, @NonNull AppWidgetHostListener listener)587 public void setListener(int appWidgetId, @NonNull AppWidgetHostListener listener) { 588 synchronized (mListeners) { 589 mListeners.put(appWidgetId, listener); 590 } 591 RemoteViews views = null; 592 try { 593 views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId); 594 } catch (RemoteException e) { 595 throw new RuntimeException("system server dead?", e); 596 } 597 listener.updateAppWidget(views); 598 } 599 600 /** 601 * Delete the listener for the given widget 602 * @param appWidgetId The ID of the app widget for which the listener is to be deleted 603 604 * @hide 605 */ removeListener(int appWidgetId)606 public void removeListener(int appWidgetId) { 607 synchronized (mListeners) { 608 mListeners.remove(appWidgetId); 609 } 610 } 611 updateAppWidgetView(int appWidgetId, RemoteViews views)612 void updateAppWidgetView(int appWidgetId, RemoteViews views) { 613 AppWidgetHostListener v = getListener(appWidgetId); 614 if (v != null) { 615 v.updateAppWidget(views); 616 } 617 } 618 viewDataChanged(int appWidgetId, int viewId)619 void viewDataChanged(int appWidgetId, int viewId) { 620 AppWidgetHostListener v = getListener(appWidgetId); 621 if (v != null) { 622 v.onViewDataChanged(viewId); 623 } 624 } 625 updateAppWidgetDeferred(int appWidgetId)626 private void updateAppWidgetDeferred(int appWidgetId) { 627 AppWidgetHostListener v = getListener(appWidgetId); 628 if (v == null) { 629 Log.e(TAG, "updateAppWidgetDeferred: null listener for id: " + appWidgetId); 630 return; 631 } 632 v.updateAppWidgetDeferred(mContextOpPackageName, appWidgetId); 633 } 634 635 /** 636 * Clear the list of Views that have been created by this AppWidgetHost. 637 */ clearViews()638 protected void clearViews() { 639 synchronized (mListeners) { 640 mListeners.clear(); 641 } 642 } 643 } 644 645 646