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<AppWidgetHostView> mViews = 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 /** 175 * @hide 176 */ 177 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) AppWidgetHost(Context context, int hostId, InteractionHandler handler, Looper looper)178 public AppWidgetHost(Context context, int hostId, InteractionHandler handler, Looper looper) { 179 mContextOpPackageName = context.getOpPackageName(); 180 mHostId = hostId; 181 mInteractionHandler = handler; 182 mHandler = new UpdateHandler(looper); 183 mCallbacks = new Callbacks(mHandler); 184 mDisplayMetrics = context.getResources().getDisplayMetrics(); 185 bindService(context); 186 } 187 bindService(Context context)188 private static void bindService(Context context) { 189 synchronized (sServiceLock) { 190 if (sServiceInitialized) { 191 return; 192 } 193 sServiceInitialized = true; 194 PackageManager packageManager = context.getPackageManager(); 195 if (!packageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS) 196 && !context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) { 197 return; 198 } 199 IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE); 200 sService = IAppWidgetService.Stub.asInterface(b); 201 } 202 } 203 204 /** 205 * Start receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity 206 * becomes visible, i.e. from onStart() in your Activity. 207 */ startListening()208 public void startListening() { 209 if (sService == null) { 210 return; 211 } 212 final int[] idsToUpdate; 213 synchronized (mViews) { 214 int N = mViews.size(); 215 idsToUpdate = new int[N]; 216 for (int i = 0; i < N; i++) { 217 idsToUpdate[i] = mViews.keyAt(i); 218 } 219 } 220 List<PendingHostUpdate> updates; 221 try { 222 updates = sService.startListening( 223 mCallbacks, mContextOpPackageName, mHostId, idsToUpdate).getList(); 224 } 225 catch (RemoteException e) { 226 throw new RuntimeException("system server dead?", e); 227 } 228 229 int N = updates.size(); 230 for (int i = 0; i < N; i++) { 231 PendingHostUpdate update = updates.get(i); 232 switch (update.type) { 233 case PendingHostUpdate.TYPE_VIEWS_UPDATE: 234 updateAppWidgetView(update.appWidgetId, update.views); 235 break; 236 case PendingHostUpdate.TYPE_PROVIDER_CHANGED: 237 onProviderChanged(update.appWidgetId, update.widgetInfo); 238 break; 239 case PendingHostUpdate.TYPE_VIEW_DATA_CHANGED: 240 viewDataChanged(update.appWidgetId, update.viewId); 241 break; 242 case PendingHostUpdate.TYPE_APP_WIDGET_REMOVED: 243 dispatchOnAppWidgetRemoved(update.appWidgetId); 244 break; 245 } 246 } 247 } 248 249 /** 250 * Stop receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity is 251 * no longer visible, i.e. from onStop() in your Activity. 252 */ stopListening()253 public void stopListening() { 254 if (sService == null) { 255 return; 256 } 257 try { 258 sService.stopListening(mContextOpPackageName, mHostId); 259 } 260 catch (RemoteException e) { 261 throw new RuntimeException("system server dead?", e); 262 } 263 } 264 265 /** 266 * Get a appWidgetId for a host in the calling process. 267 * 268 * @return a appWidgetId 269 */ allocateAppWidgetId()270 public int allocateAppWidgetId() { 271 if (sService == null) { 272 return -1; 273 } 274 try { 275 return sService.allocateAppWidgetId(mContextOpPackageName, mHostId); 276 } 277 catch (RemoteException e) { 278 throw new RuntimeException("system server dead?", e); 279 } 280 } 281 282 /** 283 * Starts an app widget provider configure activity for result on behalf of the caller. 284 * Use this method if the provider is in another profile as you are not allowed to start 285 * an activity in another profile. You can optionally provide a request code that is 286 * returned in {@link Activity#onActivityResult(int, int, android.content.Intent)} and 287 * an options bundle to be passed to the started activity. 288 * <p> 289 * Note that the provided app widget has to be bound for this method to work. 290 * </p> 291 * 292 * @param activity The activity from which to start the configure one. 293 * @param appWidgetId The bound app widget whose provider's config activity to start. 294 * @param requestCode Optional request code retuned with the result. 295 * @param intentFlags Optional intent flags. 296 * 297 * @throws android.content.ActivityNotFoundException If the activity is not found. 298 * 299 * @see AppWidgetProviderInfo#getProfile() 300 */ startAppWidgetConfigureActivityForResult(@onNull Activity activity, int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options)301 public final void startAppWidgetConfigureActivityForResult(@NonNull Activity activity, 302 int appWidgetId, int intentFlags, int requestCode, @Nullable Bundle options) { 303 if (sService == null) { 304 return; 305 } 306 try { 307 IntentSender intentSender = sService.createAppWidgetConfigIntentSender( 308 mContextOpPackageName, appWidgetId, intentFlags); 309 if (intentSender != null) { 310 activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0, 311 options); 312 } else { 313 throw new ActivityNotFoundException(); 314 } 315 } catch (IntentSender.SendIntentException e) { 316 throw new ActivityNotFoundException(); 317 } catch (RemoteException e) { 318 throw new RuntimeException("system server dead?", e); 319 } 320 } 321 322 /** 323 * Set the host's interaction handler. 324 * 325 * @hide 326 */ setInteractionHandler(InteractionHandler interactionHandler)327 public void setInteractionHandler(InteractionHandler interactionHandler) { 328 mInteractionHandler = interactionHandler; 329 } 330 331 /** 332 * Gets a list of all the appWidgetIds that are bound to the current host 333 */ getAppWidgetIds()334 public int[] getAppWidgetIds() { 335 if (sService == null) { 336 return new int[0]; 337 } 338 try { 339 return sService.getAppWidgetIdsForHost(mContextOpPackageName, mHostId); 340 } catch (RemoteException e) { 341 throw new RuntimeException("system server dead?", e); 342 } 343 } 344 345 /** 346 * Stop listening to changes for this AppWidget. 347 */ deleteAppWidgetId(int appWidgetId)348 public void deleteAppWidgetId(int appWidgetId) { 349 if (sService == null) { 350 return; 351 } 352 synchronized (mViews) { 353 mViews.remove(appWidgetId); 354 try { 355 sService.deleteAppWidgetId(mContextOpPackageName, appWidgetId); 356 } 357 catch (RemoteException e) { 358 throw new RuntimeException("system server dead?", e); 359 } 360 } 361 } 362 363 /** 364 * Remove all records about this host from the AppWidget manager. 365 * <ul> 366 * <li>Call this when initializing your database, as it might be because of a data wipe.</li> 367 * <li>Call this to have the AppWidget manager release all resources associated with your 368 * host. Any future calls about this host will cause the records to be re-allocated.</li> 369 * </ul> 370 */ deleteHost()371 public void deleteHost() { 372 if (sService == null) { 373 return; 374 } 375 try { 376 sService.deleteHost(mContextOpPackageName, mHostId); 377 } 378 catch (RemoteException e) { 379 throw new RuntimeException("system server dead?", e); 380 } 381 } 382 383 /** 384 * Remove all records about all hosts for your package. 385 * <ul> 386 * <li>Call this when initializing your database, as it might be because of a data wipe.</li> 387 * <li>Call this to have the AppWidget manager release all resources associated with your 388 * host. Any future calls about this host will cause the records to be re-allocated.</li> 389 * </ul> 390 */ deleteAllHosts()391 public static void deleteAllHosts() { 392 if (sService == null) { 393 return; 394 } 395 try { 396 sService.deleteAllHosts(); 397 } 398 catch (RemoteException e) { 399 throw new RuntimeException("system server dead?", e); 400 } 401 } 402 403 /** 404 * Create the AppWidgetHostView for the given widget. 405 * The AppWidgetHost retains a pointer to the newly-created View. 406 */ createView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget)407 public final AppWidgetHostView createView(Context context, int appWidgetId, 408 AppWidgetProviderInfo appWidget) { 409 if (sService == null) { 410 return null; 411 } 412 AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget); 413 view.setInteractionHandler(mInteractionHandler); 414 view.setAppWidget(appWidgetId, appWidget); 415 synchronized (mViews) { 416 mViews.put(appWidgetId, view); 417 } 418 RemoteViews views; 419 try { 420 views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId); 421 } catch (RemoteException e) { 422 throw new RuntimeException("system server dead?", e); 423 } 424 view.updateAppWidget(views); 425 426 return view; 427 } 428 429 /** 430 * Called to create the AppWidgetHostView. Override to return a custom subclass if you 431 * need it. {@more} 432 */ onCreateView(Context context, int appWidgetId, AppWidgetProviderInfo appWidget)433 protected AppWidgetHostView onCreateView(Context context, int appWidgetId, 434 AppWidgetProviderInfo appWidget) { 435 return new AppWidgetHostView(context, mInteractionHandler); 436 } 437 438 /** 439 * Called when the AppWidget provider for a AppWidget has been upgraded to a new apk. 440 */ onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget)441 protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) { 442 AppWidgetHostView v; 443 444 // Convert complex to dp -- we are getting the AppWidgetProviderInfo from the 445 // AppWidgetService, which doesn't have our context, hence we need to do the 446 // conversion here. 447 appWidget.updateDimensions(mDisplayMetrics); 448 synchronized (mViews) { 449 v = mViews.get(appWidgetId); 450 } 451 if (v != null) { 452 v.resetAppWidget(appWidget); 453 } 454 } 455 dispatchOnAppWidgetRemoved(int appWidgetId)456 void dispatchOnAppWidgetRemoved(int appWidgetId) { 457 synchronized (mViews) { 458 mViews.remove(appWidgetId); 459 } 460 onAppWidgetRemoved(appWidgetId); 461 } 462 463 /** 464 * Called when the app widget is removed for appWidgetId 465 * @param appWidgetId 466 */ onAppWidgetRemoved(int appWidgetId)467 public void onAppWidgetRemoved(int appWidgetId) { 468 // Does nothing 469 } 470 471 /** 472 * Called when the set of available widgets changes (ie. widget containing packages 473 * are added, updated or removed, or widget components are enabled or disabled.) 474 */ onProvidersChanged()475 protected void onProvidersChanged() { 476 // Does nothing 477 } 478 updateAppWidgetView(int appWidgetId, RemoteViews views)479 void updateAppWidgetView(int appWidgetId, RemoteViews views) { 480 AppWidgetHostView v; 481 synchronized (mViews) { 482 v = mViews.get(appWidgetId); 483 } 484 if (v != null) { 485 v.updateAppWidget(views); 486 } 487 } 488 viewDataChanged(int appWidgetId, int viewId)489 void viewDataChanged(int appWidgetId, int viewId) { 490 AppWidgetHostView v; 491 synchronized (mViews) { 492 v = mViews.get(appWidgetId); 493 } 494 if (v != null) { 495 v.viewDataChanged(viewId); 496 } 497 } 498 499 /** 500 * Clear the list of Views that have been created by this AppWidgetHost. 501 */ clearViews()502 protected void clearViews() { 503 synchronized (mViews) { 504 mViews.clear(); 505 } 506 } 507 } 508 509 510