1 /* 2 * Copyright (C) 2008 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.app.ActivityOptions; 23 import android.app.LoadedApk; 24 import android.compat.annotation.UnsupportedAppUsage; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.ContextWrapper; 28 import android.content.Intent; 29 import android.content.pm.ApplicationInfo; 30 import android.content.pm.LauncherActivityInfo; 31 import android.content.pm.LauncherApps; 32 import android.content.pm.PackageManager.NameNotFoundException; 33 import android.content.res.Resources; 34 import android.graphics.Canvas; 35 import android.graphics.Color; 36 import android.graphics.PointF; 37 import android.graphics.Rect; 38 import android.os.Build; 39 import android.os.Bundle; 40 import android.os.CancellationSignal; 41 import android.os.Parcelable; 42 import android.util.AttributeSet; 43 import android.util.Log; 44 import android.util.Pair; 45 import android.util.SizeF; 46 import android.util.SparseArray; 47 import android.util.SparseIntArray; 48 import android.view.Gravity; 49 import android.view.LayoutInflater; 50 import android.view.View; 51 import android.view.accessibility.AccessibilityNodeInfo; 52 import android.widget.Adapter; 53 import android.widget.AdapterView; 54 import android.widget.BaseAdapter; 55 import android.widget.FrameLayout; 56 import android.widget.RemoteViews; 57 import android.widget.RemoteViews.InteractionHandler; 58 import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback; 59 import android.widget.TextView; 60 61 import java.util.ArrayList; 62 import java.util.List; 63 import java.util.concurrent.Executor; 64 65 /** 66 * Provides the glue to show AppWidget views. This class offers automatic animation 67 * between updates, and will try recycling old views for each incoming 68 * {@link RemoteViews}. 69 */ 70 public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppWidgetHostListener { 71 72 static final String TAG = "AppWidgetHostView"; 73 private static final String KEY_JAILED_ARRAY = "jail"; 74 private static final String KEY_INFLATION_ID = "inflation_id"; 75 76 static final boolean LOGD = false; 77 78 static final int VIEW_MODE_NOINIT = 0; 79 static final int VIEW_MODE_CONTENT = 1; 80 static final int VIEW_MODE_ERROR = 2; 81 static final int VIEW_MODE_DEFAULT = 3; 82 83 // Set of valid colors resources. 84 private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_neutral1_0; 85 private static final int LAST_RESOURCE_COLOR_ID = android.R.color.system_accent3_1000; 86 87 // When we're inflating the initialLayout for a AppWidget, we only allow 88 // views that are allowed in RemoteViews. 89 private static final LayoutInflater.Filter INFLATER_FILTER = 90 (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class); 91 92 Context mContext; 93 Context mRemoteContext; 94 95 @UnsupportedAppUsage 96 int mAppWidgetId; 97 @UnsupportedAppUsage 98 AppWidgetProviderInfo mInfo; 99 View mView; 100 int mViewMode = VIEW_MODE_NOINIT; 101 // If true, we should not try to re-apply the RemoteViews on the next inflation. 102 boolean mColorMappingChanged = false; 103 private InteractionHandler mInteractionHandler; 104 private boolean mOnLightBackground; 105 private SizeF mCurrentSize = null; 106 private RemoteViews.ColorResources mColorResources = null; 107 // Stores the last remote views last inflated. 108 private RemoteViews mLastInflatedRemoteViews = null; 109 private long mLastInflatedRemoteViewsId = -1; 110 111 private Executor mAsyncExecutor; 112 private CancellationSignal mLastExecutionSignal; 113 private SparseArray<Parcelable> mDelayedRestoredState; 114 private long mDelayedRestoredInflationId; 115 116 /** 117 * Create a host view. Uses default fade animations. 118 */ AppWidgetHostView(Context context)119 public AppWidgetHostView(Context context) { 120 this(context, android.R.anim.fade_in, android.R.anim.fade_out); 121 } 122 123 /** 124 * @hide 125 */ AppWidgetHostView(Context context, InteractionHandler handler)126 public AppWidgetHostView(Context context, InteractionHandler handler) { 127 this(context, android.R.anim.fade_in, android.R.anim.fade_out); 128 mInteractionHandler = getHandler(handler); 129 } 130 131 /** 132 * Create a host view. Uses specified animations when pushing 133 * {@link #updateAppWidget(RemoteViews)}. 134 * 135 * @param animationIn Resource ID of in animation to use 136 * @param animationOut Resource ID of out animation to use 137 */ 138 @SuppressWarnings({"UnusedDeclaration"}) AppWidgetHostView(Context context, int animationIn, int animationOut)139 public AppWidgetHostView(Context context, int animationIn, int animationOut) { 140 super(context); 141 mContext = context; 142 // We want to segregate the view ids within AppWidgets to prevent 143 // problems when those ids collide with view ids in the AppWidgetHost. 144 setIsRootNamespace(true); 145 } 146 147 /** 148 * Pass the given handler to RemoteViews when updating this widget. Unless this 149 * is done immediatly after construction, a call to {@link #updateAppWidget(RemoteViews)} 150 * should be made. 151 * @param handler 152 * @hide 153 */ setInteractionHandler(InteractionHandler handler)154 public void setInteractionHandler(InteractionHandler handler) { 155 mInteractionHandler = getHandler(handler); 156 } 157 158 /** 159 * @hide 160 */ 161 public static class AdapterChildHostView extends AppWidgetHostView { 162 AdapterChildHostView(Context context)163 public AdapterChildHostView(Context context) { 164 super(context); 165 } 166 167 @Override getRemoteContextEnsuringCorrectCachedApkPath()168 public Context getRemoteContextEnsuringCorrectCachedApkPath() { 169 // To reduce noise in error messages 170 return null; 171 } 172 } 173 174 /** 175 * Set the AppWidget that will be displayed by this view. This method also adds default padding 176 * to widgets, as described in {@link #getDefaultPaddingForWidget(Context, ComponentName, Rect)} 177 * and can be overridden in order to add custom padding. 178 */ setAppWidget(int appWidgetId, AppWidgetProviderInfo info)179 public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) { 180 mAppWidgetId = appWidgetId; 181 mInfo = info; 182 183 // We add padding to the AppWidgetHostView if necessary 184 Rect padding = getDefaultPadding(); 185 setPadding(padding.left, padding.top, padding.right, padding.bottom); 186 187 // Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for 188 // a widget, eg. for some widgets in safe mode. 189 if (info != null) { 190 String description = info.loadLabel(getContext().getPackageManager()); 191 if ((info.providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) { 192 description = Resources.getSystem().getString( 193 com.android.internal.R.string.suspended_widget_accessibility, description); 194 } 195 setContentDescription(description); 196 } 197 } 198 199 /** 200 * As of ICE_CREAM_SANDWICH we are automatically adding padding to widgets targeting 201 * ICE_CREAM_SANDWICH and higher. The new widget design guidelines strongly recommend 202 * that widget developers do not add extra padding to their widgets. This will help 203 * achieve consistency among widgets. 204 * 205 * Note: this method is only needed by developers of AppWidgetHosts. The method is provided in 206 * order for the AppWidgetHost to account for the automatic padding when computing the number 207 * of cells to allocate to a particular widget. 208 * 209 * @param context the current context 210 * @param component the component name of the widget 211 * @param padding Rect in which to place the output, if null, a new Rect will be allocated and 212 * returned 213 * @return default padding for this widget, in pixels 214 */ getDefaultPaddingForWidget(Context context, ComponentName component, Rect padding)215 public static Rect getDefaultPaddingForWidget(Context context, ComponentName component, 216 Rect padding) { 217 return getDefaultPaddingForWidget(context, padding); 218 } 219 getDefaultPaddingForWidget(Context context, Rect padding)220 private static Rect getDefaultPaddingForWidget(Context context, Rect padding) { 221 if (padding == null) { 222 padding = new Rect(0, 0, 0, 0); 223 } else { 224 padding.set(0, 0, 0, 0); 225 } 226 Resources r = context.getResources(); 227 padding.left = r.getDimensionPixelSize( 228 com.android.internal.R.dimen.default_app_widget_padding_left); 229 padding.right = r.getDimensionPixelSize( 230 com.android.internal.R.dimen.default_app_widget_padding_right); 231 padding.top = r.getDimensionPixelSize( 232 com.android.internal.R.dimen.default_app_widget_padding_top); 233 padding.bottom = r.getDimensionPixelSize( 234 com.android.internal.R.dimen.default_app_widget_padding_bottom); 235 return padding; 236 } 237 getDefaultPadding()238 private Rect getDefaultPadding() { 239 return getDefaultPaddingForWidget(mContext, null); 240 } 241 getAppWidgetId()242 public int getAppWidgetId() { 243 return mAppWidgetId; 244 } 245 getAppWidgetInfo()246 public AppWidgetProviderInfo getAppWidgetInfo() { 247 return mInfo; 248 } 249 250 @Override dispatchSaveInstanceState(SparseArray<Parcelable> container)251 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { 252 final SparseArray<Parcelable> jail = new SparseArray<>(); 253 super.dispatchSaveInstanceState(jail); 254 255 Bundle bundle = new Bundle(); 256 bundle.putSparseParcelableArray(KEY_JAILED_ARRAY, jail); 257 bundle.putLong(KEY_INFLATION_ID, mLastInflatedRemoteViewsId); 258 container.put(generateId(), bundle); 259 container.put(generateId(), bundle); 260 } 261 generateId()262 private int generateId() { 263 final int id = getId(); 264 return id == View.NO_ID ? mAppWidgetId : id; 265 } 266 267 @Override dispatchRestoreInstanceState(SparseArray<Parcelable> container)268 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 269 final Parcelable parcelable = container.get(generateId()); 270 271 SparseArray<Parcelable> jail = null; 272 long inflationId = -1; 273 if (parcelable instanceof Bundle) { 274 Bundle bundle = (Bundle) parcelable; 275 jail = bundle.getSparseParcelableArray(KEY_JAILED_ARRAY); 276 inflationId = bundle.getLong(KEY_INFLATION_ID, -1); 277 } 278 279 if (jail == null) jail = new SparseArray<>(); 280 281 mDelayedRestoredState = jail; 282 mDelayedRestoredInflationId = inflationId; 283 restoreInstanceState(); 284 } 285 restoreInstanceState()286 void restoreInstanceState() { 287 long inflationId = mDelayedRestoredInflationId; 288 SparseArray<Parcelable> state = mDelayedRestoredState; 289 if (inflationId == -1 || inflationId != mLastInflatedRemoteViewsId) { 290 return; // We don't restore. 291 } 292 mDelayedRestoredInflationId = -1; 293 mDelayedRestoredState = null; 294 try { 295 super.dispatchRestoreInstanceState(state); 296 } catch (Exception e) { 297 Log.e(TAG, "failed to restoreInstanceState for widget id: " + mAppWidgetId + ", " 298 + (mInfo == null ? "null" : mInfo.provider), e); 299 } 300 } 301 computeSizeFromLayout(int left, int top, int right, int bottom)302 private SizeF computeSizeFromLayout(int left, int top, int right, int bottom) { 303 float density = getResources().getDisplayMetrics().density; 304 return new SizeF( 305 (right - left - getPaddingLeft() - getPaddingRight()) / density, 306 (bottom - top - getPaddingTop() - getPaddingBottom()) / density 307 ); 308 } 309 310 @Override onLayout(boolean changed, int left, int top, int right, int bottom)311 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 312 try { 313 SizeF oldSize = mCurrentSize; 314 SizeF newSize = computeSizeFromLayout(left, top, right, bottom); 315 mCurrentSize = newSize; 316 if (mLastInflatedRemoteViews != null) { 317 RemoteViews toApply = mLastInflatedRemoteViews.getRemoteViewsToApplyIfDifferent( 318 oldSize, newSize); 319 if (toApply != null) { 320 applyRemoteViews(toApply, false); 321 measureChildWithMargins(mView, 322 MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), 323 0 /* widthUsed */, 324 MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY), 325 0 /* heightUsed */); 326 } 327 } 328 super.onLayout(changed, left, top, right, bottom); 329 } catch (final RuntimeException e) { 330 Log.e(TAG, "Remote provider threw runtime exception, using error view instead.", e); 331 handleViewError(); 332 } 333 } 334 335 /** 336 * Remove bad view and replace with error message view 337 */ handleViewError()338 private void handleViewError() { 339 removeViewInLayout(mView); 340 View child = getErrorView(); 341 prepareView(child); 342 addViewInLayout(child, 0, child.getLayoutParams()); 343 measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), 344 MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); 345 child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight, 346 child.getMeasuredHeight() + mPaddingTop + mPaddingBottom); 347 mView = child; 348 mViewMode = VIEW_MODE_ERROR; 349 } 350 351 /** 352 * Provide guidance about the size of this widget to the AppWidgetManager. The widths and 353 * heights should correspond to the full area the AppWidgetHostView is given. Padding added by 354 * the framework will be accounted for automatically. This information gets embedded into the 355 * AppWidget options and causes a callback to the AppWidgetProvider. In addition, the list of 356 * sizes is explicitly set to an empty list. 357 * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle) 358 * 359 * @param newOptions The bundle of options, in addition to the size information, 360 * can be null. 361 * @param minWidth The minimum width in dips that the widget will be displayed at. 362 * @param minHeight The maximum height in dips that the widget will be displayed at. 363 * @param maxWidth The maximum width in dips that the widget will be displayed at. 364 * @param maxHeight The maximum height in dips that the widget will be displayed at. 365 * @deprecated use {@link AppWidgetHostView#updateAppWidgetSize(Bundle, List)} instead. 366 */ 367 @Deprecated updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight)368 public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, 369 int maxHeight) { 370 updateAppWidgetSize(newOptions, minWidth, minHeight, maxWidth, maxHeight, false); 371 } 372 373 /** 374 * Provide guidance about the size of this widget to the AppWidgetManager. The sizes should 375 * correspond to the full area the AppWidgetHostView is given. Padding added by the framework 376 * will be accounted for automatically. 377 * 378 * This method will update the option bundle with the list of sizes and the min/max bounds for 379 * width and height. 380 * 381 * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle) 382 * 383 * @param newOptions The bundle of options, in addition to the size information. 384 * @param sizes Sizes, in dips, the widget may be displayed at without calling the provider 385 * again. Typically, this will be size of the widget in landscape and portrait. 386 * On some foldables, this might include the size on the outer and inner screens. 387 */ updateAppWidgetSize(@onNull Bundle newOptions, @NonNull List<SizeF> sizes)388 public void updateAppWidgetSize(@NonNull Bundle newOptions, @NonNull List<SizeF> sizes) { 389 AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext); 390 391 Rect padding = getDefaultPadding(); 392 float density = getResources().getDisplayMetrics().density; 393 394 float xPaddingDips = (padding.left + padding.right) / density; 395 float yPaddingDips = (padding.top + padding.bottom) / density; 396 397 ArrayList<SizeF> paddedSizes = new ArrayList<>(sizes.size()); 398 float minWidth = Float.MAX_VALUE; 399 float maxWidth = 0; 400 float minHeight = Float.MAX_VALUE; 401 float maxHeight = 0; 402 for (int i = 0; i < sizes.size(); i++) { 403 SizeF size = sizes.get(i); 404 SizeF paddedSize = new SizeF(Math.max(0.f, size.getWidth() - xPaddingDips), 405 Math.max(0.f, size.getHeight() - yPaddingDips)); 406 paddedSizes.add(paddedSize); 407 minWidth = Math.min(minWidth, paddedSize.getWidth()); 408 maxWidth = Math.max(maxWidth, paddedSize.getWidth()); 409 minHeight = Math.min(minHeight, paddedSize.getHeight()); 410 maxHeight = Math.max(maxHeight, paddedSize.getHeight()); 411 } 412 if (paddedSizes.equals( 413 widgetManager.getAppWidgetOptions(mAppWidgetId).<SizeF>getParcelableArrayList( 414 AppWidgetManager.OPTION_APPWIDGET_SIZES))) { 415 return; 416 } 417 Bundle options = newOptions.deepCopy(); 418 options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, (int) minWidth); 419 options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, (int) minHeight); 420 options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, (int) maxWidth); 421 options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, (int) maxHeight); 422 options.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, paddedSizes); 423 updateAppWidgetOptions(options); 424 } 425 426 /** 427 * @hide 428 */ 429 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight, boolean ignorePadding)430 public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, 431 int maxHeight, boolean ignorePadding) { 432 if (newOptions == null) { 433 newOptions = new Bundle(); 434 } 435 436 Rect padding = getDefaultPadding(); 437 float density = getResources().getDisplayMetrics().density; 438 439 int xPaddingDips = (int) ((padding.left + padding.right) / density); 440 int yPaddingDips = (int) ((padding.top + padding.bottom) / density); 441 442 int newMinWidth = minWidth - (ignorePadding ? 0 : xPaddingDips); 443 int newMinHeight = minHeight - (ignorePadding ? 0 : yPaddingDips); 444 int newMaxWidth = maxWidth - (ignorePadding ? 0 : xPaddingDips); 445 int newMaxHeight = maxHeight - (ignorePadding ? 0 : yPaddingDips); 446 447 AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext); 448 449 // We get the old options to see if the sizes have changed 450 Bundle oldOptions = widgetManager.getAppWidgetOptions(mAppWidgetId); 451 boolean needsUpdate = false; 452 if (newMinWidth != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) || 453 newMinHeight != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) || 454 newMaxWidth != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH) || 455 newMaxHeight != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)) { 456 needsUpdate = true; 457 } 458 459 if (needsUpdate) { 460 newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, newMinWidth); 461 newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, newMinHeight); 462 newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, newMaxWidth); 463 newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, newMaxHeight); 464 newOptions.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES, 465 new ArrayList<PointF>()); 466 updateAppWidgetOptions(newOptions); 467 } 468 } 469 470 /** 471 * Specify some extra information for the widget provider. Causes a callback to the 472 * AppWidgetProvider. 473 * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle) 474 * 475 * @param options The bundle of options information. 476 */ updateAppWidgetOptions(Bundle options)477 public void updateAppWidgetOptions(Bundle options) { 478 AppWidgetManager.getInstance(mContext).updateAppWidgetOptions(mAppWidgetId, options); 479 } 480 481 /** {@inheritDoc} */ 482 @Override generateLayoutParams(AttributeSet attrs)483 public LayoutParams generateLayoutParams(AttributeSet attrs) { 484 // We're being asked to inflate parameters, probably by a LayoutInflater 485 // in a remote Context. To help resolve any remote references, we 486 // inflate through our last mRemoteContext when it exists. 487 final Context context = mRemoteContext != null ? mRemoteContext : mContext; 488 return new FrameLayout.LayoutParams(context, attrs); 489 } 490 491 /** 492 * Sets an executor which can be used for asynchronously inflating. CPU intensive tasks like 493 * view inflation or loading images will be performed on the executor. The updates will still 494 * be applied on the UI thread. 495 * 496 * @param executor the executor to use or null. 497 */ setExecutor(Executor executor)498 public void setExecutor(Executor executor) { 499 if (mLastExecutionSignal != null) { 500 mLastExecutionSignal.cancel(); 501 mLastExecutionSignal = null; 502 } 503 504 mAsyncExecutor = executor; 505 } 506 507 /** 508 * Sets whether the widget is being displayed on a light/white background and use an 509 * alternate UI if available. 510 * @see RemoteViews#setLightBackgroundLayoutId(int) 511 */ setOnLightBackground(boolean onLightBackground)512 public void setOnLightBackground(boolean onLightBackground) { 513 mOnLightBackground = onLightBackground; 514 } 515 516 /** 517 * Update the AppWidgetProviderInfo for this view, and reset it to the 518 * initial layout. 519 * 520 * @hide 521 */ 522 @Override onUpdateProviderInfo(@ullable AppWidgetProviderInfo info)523 public void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo info) { 524 setAppWidget(mAppWidgetId, info); 525 mViewMode = VIEW_MODE_NOINIT; 526 updateAppWidget(null); 527 } 528 529 /** 530 * Process a set of {@link RemoteViews} coming in as an update from the 531 * AppWidget provider. Will animate into these new views as needed 532 */ 533 @Override updateAppWidget(RemoteViews remoteViews)534 public void updateAppWidget(RemoteViews remoteViews) { 535 mLastInflatedRemoteViews = remoteViews; 536 applyRemoteViews(remoteViews, true); 537 } 538 539 /** 540 * Reapply the last inflated remote views, or the default view is none was inflated. 541 */ reapplyLastRemoteViews()542 private void reapplyLastRemoteViews() { 543 SparseArray<Parcelable> savedState = new SparseArray<>(); 544 saveHierarchyState(savedState); 545 applyRemoteViews(mLastInflatedRemoteViews, true); 546 restoreHierarchyState(savedState); 547 } 548 549 /** 550 * @hide 551 */ applyRemoteViews(@ullable RemoteViews remoteViews, boolean useAsyncIfPossible)552 protected void applyRemoteViews(@Nullable RemoteViews remoteViews, boolean useAsyncIfPossible) { 553 boolean recycled = false; 554 View content = null; 555 Exception exception = null; 556 557 // Block state restore until the end of the apply. 558 mLastInflatedRemoteViewsId = -1; 559 560 if (mLastExecutionSignal != null) { 561 mLastExecutionSignal.cancel(); 562 mLastExecutionSignal = null; 563 } 564 565 if (remoteViews == null) { 566 if (mViewMode == VIEW_MODE_DEFAULT) { 567 // We've already done this -- nothing to do. 568 return; 569 } 570 content = getDefaultView(); 571 mViewMode = VIEW_MODE_DEFAULT; 572 } else { 573 // Select the remote view we are actually going to apply. 574 RemoteViews rvToApply = remoteViews.getRemoteViewsToApply(mContext, mCurrentSize); 575 if (mOnLightBackground) { 576 rvToApply = rvToApply.getDarkTextViews(); 577 } 578 579 if (mAsyncExecutor != null && useAsyncIfPossible) { 580 inflateAsync(rvToApply); 581 return; 582 } 583 // Prepare a local reference to the remote Context so we're ready to 584 // inflate any requested LayoutParams. 585 mRemoteContext = getRemoteContextEnsuringCorrectCachedApkPath(); 586 587 if (!mColorMappingChanged && rvToApply.canRecycleView(mView)) { 588 try { 589 rvToApply.reapply(mContext, mView, mInteractionHandler, mCurrentSize, 590 mColorResources); 591 content = mView; 592 mLastInflatedRemoteViewsId = rvToApply.computeUniqueId(remoteViews); 593 recycled = true; 594 if (LOGD) Log.d(TAG, "was able to recycle existing layout"); 595 } catch (RuntimeException e) { 596 exception = e; 597 } 598 } 599 600 // Try normal RemoteView inflation 601 if (content == null) { 602 try { 603 content = rvToApply.apply(mContext, this, mInteractionHandler, 604 mCurrentSize, mColorResources); 605 mLastInflatedRemoteViewsId = rvToApply.computeUniqueId(remoteViews); 606 if (LOGD) Log.d(TAG, "had to inflate new layout"); 607 } catch (RuntimeException e) { 608 exception = e; 609 } 610 } 611 612 mViewMode = VIEW_MODE_CONTENT; 613 } 614 615 applyContent(content, recycled, exception); 616 } 617 applyContent(View content, boolean recycled, Exception exception)618 private void applyContent(View content, boolean recycled, Exception exception) { 619 mColorMappingChanged = false; 620 if (content == null) { 621 if (mViewMode == VIEW_MODE_ERROR) { 622 // We've already done this -- nothing to do. 623 return ; 624 } 625 if (exception != null) { 626 Log.w(TAG, "Error inflating RemoteViews", exception); 627 } 628 content = getErrorView(); 629 mViewMode = VIEW_MODE_ERROR; 630 } 631 632 if (!recycled) { 633 prepareView(content); 634 addView(content); 635 } 636 637 if (mView != content) { 638 removeView(mView); 639 mView = content; 640 } 641 } 642 inflateAsync(@onNull RemoteViews remoteViews)643 private void inflateAsync(@NonNull RemoteViews remoteViews) { 644 // Prepare a local reference to the remote Context so we're ready to 645 // inflate any requested LayoutParams. 646 mRemoteContext = getRemoteContextEnsuringCorrectCachedApkPath(); 647 int layoutId = remoteViews.getLayoutId(); 648 649 if (mLastExecutionSignal != null) { 650 mLastExecutionSignal.cancel(); 651 } 652 653 // If our stale view has been prepared to match active, and the new 654 // layout matches, try recycling it 655 if (!mColorMappingChanged && remoteViews.canRecycleView(mView)) { 656 try { 657 mLastExecutionSignal = remoteViews.reapplyAsync(mContext, 658 mView, 659 mAsyncExecutor, 660 new ViewApplyListener(remoteViews, layoutId, true), 661 mInteractionHandler, 662 mCurrentSize, 663 mColorResources); 664 } catch (Exception e) { 665 // Reapply failed. Try apply 666 } 667 } 668 if (mLastExecutionSignal == null) { 669 mLastExecutionSignal = remoteViews.applyAsync(mContext, 670 this, 671 mAsyncExecutor, 672 new ViewApplyListener(remoteViews, layoutId, false), 673 mInteractionHandler, 674 mCurrentSize, 675 mColorResources); 676 } 677 } 678 679 private class ViewApplyListener implements RemoteViews.OnViewAppliedListener { 680 private final RemoteViews mViews; 681 private final boolean mIsReapply; 682 private final int mLayoutId; 683 ViewApplyListener( RemoteViews views, int layoutId, boolean isReapply)684 ViewApplyListener( 685 RemoteViews views, 686 int layoutId, 687 boolean isReapply) { 688 mViews = views; 689 mLayoutId = layoutId; 690 mIsReapply = isReapply; 691 } 692 693 @Override onViewApplied(View v)694 public void onViewApplied(View v) { 695 mViewMode = VIEW_MODE_CONTENT; 696 697 applyContent(v, mIsReapply, null); 698 699 mLastInflatedRemoteViewsId = mViews.computeUniqueId(mLastInflatedRemoteViews); 700 restoreInstanceState(); 701 mLastExecutionSignal = null; 702 } 703 704 @Override onError(Exception e)705 public void onError(Exception e) { 706 if (mIsReapply) { 707 // Try a fresh replay 708 mLastExecutionSignal = mViews.applyAsync(mContext, 709 AppWidgetHostView.this, 710 mAsyncExecutor, 711 new ViewApplyListener(mViews, mLayoutId, false), 712 mInteractionHandler, 713 mCurrentSize); 714 } else { 715 applyContent(null, false, e); 716 } 717 mLastExecutionSignal = null; 718 } 719 } 720 721 /** 722 * Process data-changed notifications for the specified view in the specified 723 * set of {@link RemoteViews} views. 724 * 725 * @hide 726 */ 727 @Override onViewDataChanged(int viewId)728 public void onViewDataChanged(int viewId) { 729 View v = findViewById(viewId); 730 if ((v != null) && (v instanceof AdapterView<?>)) { 731 AdapterView<?> adapterView = (AdapterView<?>) v; 732 Adapter adapter = adapterView.getAdapter(); 733 if (adapter instanceof BaseAdapter) { 734 BaseAdapter baseAdapter = (BaseAdapter) adapter; 735 baseAdapter.notifyDataSetChanged(); 736 } else if (adapter == null && adapterView instanceof RemoteAdapterConnectionCallback) { 737 // If the adapter is null, it may mean that the RemoteViewsAapter has not yet 738 // connected to its associated service, and hence the adapter hasn't been set. 739 // In this case, we need to defer the notify call until it has been set. 740 ((RemoteAdapterConnectionCallback) adapterView).deferNotifyDataSetChanged(); 741 } 742 } 743 } 744 745 /** 746 * Build a {@link Context} cloned into another package name, usually for the 747 * purposes of reading remote resources. 748 * @hide 749 */ getRemoteContextEnsuringCorrectCachedApkPath()750 protected Context getRemoteContextEnsuringCorrectCachedApkPath() { 751 try { 752 ApplicationInfo expectedAppInfo = mInfo.providerInfo.applicationInfo; 753 LoadedApk.checkAndUpdateApkPaths(expectedAppInfo); 754 // Return if cloned successfully, otherwise default 755 Context newContext = mContext.createApplicationContext( 756 mInfo.providerInfo.applicationInfo, 757 Context.CONTEXT_RESTRICTED); 758 if (mColorResources != null) { 759 mColorResources.apply(newContext); 760 } 761 return newContext; 762 } catch (NameNotFoundException e) { 763 Log.e(TAG, "Package name " + mInfo.providerInfo.packageName + " not found"); 764 return mContext; 765 } catch (NullPointerException e) { 766 Log.e(TAG, "Error trying to create the remote context.", e); 767 return mContext; 768 } 769 } 770 771 /** 772 * Prepare the given view to be shown. This might include adjusting 773 * {@link FrameLayout.LayoutParams} before inserting. 774 */ prepareView(View view)775 protected void prepareView(View view) { 776 // Take requested dimensions from child, but apply default gravity. 777 FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams)view.getLayoutParams(); 778 if (requested == null) { 779 requested = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, 780 LayoutParams.MATCH_PARENT); 781 } 782 783 requested.gravity = Gravity.CENTER; 784 view.setLayoutParams(requested); 785 } 786 787 /** 788 * Inflate and return the default layout requested by AppWidget provider. 789 */ getDefaultView()790 protected View getDefaultView() { 791 if (LOGD) { 792 Log.d(TAG, "getDefaultView"); 793 } 794 View defaultView = null; 795 Exception exception = null; 796 797 try { 798 if (mInfo != null) { 799 Context theirContext = getRemoteContextEnsuringCorrectCachedApkPath(); 800 mRemoteContext = theirContext; 801 LayoutInflater inflater = (LayoutInflater) 802 theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 803 inflater = inflater.cloneInContext(theirContext); 804 inflater.setFilter(INFLATER_FILTER); 805 AppWidgetManager manager = AppWidgetManager.getInstance(mContext); 806 Bundle options = manager.getAppWidgetOptions(mAppWidgetId); 807 808 int layoutId = mInfo.initialLayout; 809 if (options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) { 810 int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY); 811 if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) { 812 int kgLayoutId = mInfo.initialKeyguardLayout; 813 // If a default keyguard layout is not specified, use the standard 814 // default layout. 815 layoutId = kgLayoutId == 0 ? layoutId : kgLayoutId; 816 } 817 } 818 defaultView = inflater.inflate(layoutId, this, false); 819 if (!(defaultView instanceof AdapterView)) { 820 // AdapterView does not support onClickListener 821 defaultView.setOnClickListener(this::onDefaultViewClicked); 822 } 823 } else { 824 Log.w(TAG, "can't inflate defaultView because mInfo is missing"); 825 } 826 } catch (RuntimeException e) { 827 exception = e; 828 } 829 830 if (exception != null) { 831 Log.w(TAG, "Error inflating AppWidget " + mInfo, exception); 832 } 833 834 if (defaultView == null) { 835 if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error"); 836 defaultView = getErrorView(); 837 } 838 839 return defaultView; 840 } 841 onDefaultViewClicked(View view)842 private void onDefaultViewClicked(View view) { 843 if (mInfo != null) { 844 LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class); 845 List<LauncherActivityInfo> activities = launcherApps.getActivityList( 846 mInfo.provider.getPackageName(), mInfo.getProfile()); 847 if (!activities.isEmpty()) { 848 LauncherActivityInfo ai = activities.get(0); 849 launcherApps.startMainActivity(ai.getComponentName(), ai.getUser(), 850 RemoteViews.getSourceBounds(view), null); 851 } 852 } 853 } 854 855 /** 856 * Inflate and return a view that represents an error state. 857 */ getErrorView()858 protected View getErrorView() { 859 TextView tv = new TextView(mContext); 860 tv.setText(com.android.internal.R.string.gadget_host_error_inflating); 861 // TODO: get this color from somewhere. 862 tv.setBackgroundColor(Color.argb(127, 0, 0, 0)); 863 return tv; 864 } 865 866 /** @hide */ 867 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)868 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 869 super.onInitializeAccessibilityNodeInfoInternal(info); 870 info.setClassName(AppWidgetHostView.class.getName()); 871 } 872 873 /** @hide */ createSharedElementActivityOptions( int[] sharedViewIds, String[] sharedViewNames, Intent fillInIntent)874 public ActivityOptions createSharedElementActivityOptions( 875 int[] sharedViewIds, String[] sharedViewNames, Intent fillInIntent) { 876 Context parentContext = getContext(); 877 while ((parentContext instanceof ContextWrapper) 878 && !(parentContext instanceof Activity)) { 879 parentContext = ((ContextWrapper) parentContext).getBaseContext(); 880 } 881 if (!(parentContext instanceof Activity)) { 882 return null; 883 } 884 885 List<Pair<View, String>> sharedElements = new ArrayList<>(); 886 Bundle extras = new Bundle(); 887 888 for (int i = 0; i < sharedViewIds.length; i++) { 889 View view = findViewById(sharedViewIds[i]); 890 if (view != null) { 891 sharedElements.add(Pair.create(view, sharedViewNames[i])); 892 893 extras.putParcelable(sharedViewNames[i], RemoteViews.getSourceBounds(view)); 894 } 895 } 896 897 if (!sharedElements.isEmpty()) { 898 fillInIntent.putExtra(RemoteViews.EXTRA_SHARED_ELEMENT_BOUNDS, extras); 899 final ActivityOptions opts = ActivityOptions.makeSceneTransitionAnimation( 900 (Activity) parentContext, 901 sharedElements.toArray(new Pair[sharedElements.size()])); 902 opts.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 903 return opts; 904 } 905 return null; 906 } 907 getHandler(InteractionHandler handler)908 private InteractionHandler getHandler(InteractionHandler handler) { 909 return (view, pendingIntent, response) -> { 910 AppWidgetManager manager = AppWidgetManager.getInstance(mContext); 911 if (manager != null) { 912 manager.noteAppWidgetTapped(mAppWidgetId); 913 } 914 if (handler != null) { 915 return handler.onInteraction(view, pendingIntent, response); 916 } else { 917 return RemoteViews.startPendingIntent(view, pendingIntent, 918 response.getLaunchOptions(view)); 919 } 920 }; 921 } 922 923 /** 924 * Set the dynamically overloaded color resources. 925 * 926 * {@code colorMapping} maps a predefined set of color resources to their ARGB 927 * representation. Any entry not in the predefined set of colors will be ignored. 928 * 929 * Calling this method will trigger a full re-inflation of the App Widget. 930 * 931 * The color resources that can be overloaded are the ones whose name is prefixed with 932 * {@code system_neutral} or {@code system_accent}, for example 933 * {@link android.R.color#system_neutral1_500}. 934 */ setColorResources(@onNull SparseIntArray colorMapping)935 public void setColorResources(@NonNull SparseIntArray colorMapping) { 936 if (mColorResources != null 937 && isSameColorMapping(mColorResources.getColorMapping(), colorMapping)) { 938 return; 939 } 940 setColorResources(RemoteViews.ColorResources.create(mContext, colorMapping)); 941 } 942 setColorResourcesStates(RemoteViews.ColorResources colorResources)943 private void setColorResourcesStates(RemoteViews.ColorResources colorResources) { 944 mColorResources = colorResources; 945 mColorMappingChanged = true; 946 mViewMode = VIEW_MODE_NOINIT; 947 } 948 949 /** @hide **/ setColorResources(RemoteViews.ColorResources colorResources)950 public void setColorResources(RemoteViews.ColorResources colorResources) { 951 if (colorResources == mColorResources) { 952 return; 953 } 954 setColorResourcesStates(colorResources); 955 reapplyLastRemoteViews(); 956 } 957 958 /** 959 * @hide 960 */ setColorResourcesNoReapply(RemoteViews.ColorResources colorResources)961 public void setColorResourcesNoReapply(RemoteViews.ColorResources colorResources) { 962 if (colorResources == mColorResources) { 963 return; 964 } 965 setColorResourcesStates(colorResources); 966 } 967 968 /** Check if, in the current context, the two color mappings are equivalent. */ isSameColorMapping(SparseIntArray oldColors, SparseIntArray newColors)969 private boolean isSameColorMapping(SparseIntArray oldColors, SparseIntArray newColors) { 970 if (oldColors.size() != newColors.size()) { 971 return false; 972 } 973 for (int i = 0; i < oldColors.size(); i++) { 974 if (oldColors.keyAt(i) != newColors.keyAt(i) 975 || oldColors.valueAt(i) != newColors.valueAt(i)) { 976 return false; 977 } 978 } 979 return true; 980 } 981 982 /** 983 * Reset the dynamically overloaded resources, reverting to the default values for 984 * all the colors. 985 * 986 * If colors were defined before, calling this method will trigger a full re-inflation of the 987 * App Widget. 988 */ resetColorResources()989 public void resetColorResources() { 990 if (mColorResources != null) { 991 mColorResources = null; 992 mColorMappingChanged = true; 993 mViewMode = VIEW_MODE_NOINIT; 994 reapplyLastRemoteViews(); 995 } 996 } 997 998 @Override dispatchDraw(@onNull Canvas canvas)999 protected void dispatchDraw(@NonNull Canvas canvas) { 1000 try { 1001 super.dispatchDraw(canvas); 1002 } catch (Exception e) { 1003 // Catch draw exceptions that may be caused by RemoteViews 1004 Log.e(TAG, "Drawing view failed: " + e); 1005 post(this::handleViewError); 1006 } 1007 } 1008 } 1009