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