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.app.Activity; 20 import android.app.ActivityOptions; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.ContextWrapper; 25 import android.content.Intent; 26 import android.content.pm.ApplicationInfo; 27 import android.content.pm.LauncherActivityInfo; 28 import android.content.pm.LauncherApps; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.content.res.Resources; 31 import android.graphics.Color; 32 import android.graphics.Rect; 33 import android.os.Bundle; 34 import android.os.CancellationSignal; 35 import android.os.Parcelable; 36 import android.util.AttributeSet; 37 import android.util.Log; 38 import android.util.Pair; 39 import android.util.SparseArray; 40 import android.view.Gravity; 41 import android.view.LayoutInflater; 42 import android.view.View; 43 import android.view.accessibility.AccessibilityNodeInfo; 44 import android.widget.Adapter; 45 import android.widget.AdapterView; 46 import android.widget.BaseAdapter; 47 import android.widget.FrameLayout; 48 import android.widget.RemoteViews; 49 import android.widget.RemoteViews.OnClickHandler; 50 import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback; 51 import android.widget.TextView; 52 53 import java.util.ArrayList; 54 import java.util.List; 55 import java.util.concurrent.Executor; 56 57 /** 58 * Provides the glue to show AppWidget views. This class offers automatic animation 59 * between updates, and will try recycling old views for each incoming 60 * {@link RemoteViews}. 61 */ 62 public class AppWidgetHostView extends FrameLayout { 63 64 static final String TAG = "AppWidgetHostView"; 65 private static final String KEY_JAILED_ARRAY = "jail"; 66 67 static final boolean LOGD = false; 68 69 static final int VIEW_MODE_NOINIT = 0; 70 static final int VIEW_MODE_CONTENT = 1; 71 static final int VIEW_MODE_ERROR = 2; 72 static final int VIEW_MODE_DEFAULT = 3; 73 74 // When we're inflating the initialLayout for a AppWidget, we only allow 75 // views that are allowed in RemoteViews. 76 private static final LayoutInflater.Filter INFLATER_FILTER = 77 (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class); 78 79 Context mContext; 80 Context mRemoteContext; 81 82 @UnsupportedAppUsage 83 int mAppWidgetId; 84 @UnsupportedAppUsage 85 AppWidgetProviderInfo mInfo; 86 View mView; 87 int mViewMode = VIEW_MODE_NOINIT; 88 int mLayoutId = -1; 89 private OnClickHandler mOnClickHandler; 90 private boolean mOnLightBackground; 91 92 private Executor mAsyncExecutor; 93 private CancellationSignal mLastExecutionSignal; 94 95 /** 96 * Create a host view. Uses default fade animations. 97 */ AppWidgetHostView(Context context)98 public AppWidgetHostView(Context context) { 99 this(context, android.R.anim.fade_in, android.R.anim.fade_out); 100 } 101 102 /** 103 * @hide 104 */ AppWidgetHostView(Context context, OnClickHandler handler)105 public AppWidgetHostView(Context context, OnClickHandler handler) { 106 this(context, android.R.anim.fade_in, android.R.anim.fade_out); 107 mOnClickHandler = getHandler(handler); 108 } 109 110 /** 111 * Create a host view. Uses specified animations when pushing 112 * {@link #updateAppWidget(RemoteViews)}. 113 * 114 * @param animationIn Resource ID of in animation to use 115 * @param animationOut Resource ID of out animation to use 116 */ 117 @SuppressWarnings({"UnusedDeclaration"}) AppWidgetHostView(Context context, int animationIn, int animationOut)118 public AppWidgetHostView(Context context, int animationIn, int animationOut) { 119 super(context); 120 mContext = context; 121 // We want to segregate the view ids within AppWidgets to prevent 122 // problems when those ids collide with view ids in the AppWidgetHost. 123 setIsRootNamespace(true); 124 } 125 126 /** 127 * Pass the given handler to RemoteViews when updating this widget. Unless this 128 * is done immediatly after construction, a call to {@link #updateAppWidget(RemoteViews)} 129 * should be made. 130 * @param handler 131 * @hide 132 */ setOnClickHandler(OnClickHandler handler)133 public void setOnClickHandler(OnClickHandler handler) { 134 mOnClickHandler = getHandler(handler); 135 } 136 137 /** 138 * Set the AppWidget that will be displayed by this view. This method also adds default padding 139 * to widgets, as described in {@link #getDefaultPaddingForWidget(Context, ComponentName, Rect)} 140 * and can be overridden in order to add custom padding. 141 */ setAppWidget(int appWidgetId, AppWidgetProviderInfo info)142 public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) { 143 mAppWidgetId = appWidgetId; 144 mInfo = info; 145 146 // We add padding to the AppWidgetHostView if necessary 147 Rect padding = getDefaultPadding(); 148 setPadding(padding.left, padding.top, padding.right, padding.bottom); 149 150 // Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for 151 // a widget, eg. for some widgets in safe mode. 152 if (info != null) { 153 String description = info.loadLabel(getContext().getPackageManager()); 154 if ((info.providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) { 155 description = Resources.getSystem().getString( 156 com.android.internal.R.string.suspended_widget_accessibility, description); 157 } 158 setContentDescription(description); 159 } 160 } 161 162 /** 163 * As of ICE_CREAM_SANDWICH we are automatically adding padding to widgets targeting 164 * ICE_CREAM_SANDWICH and higher. The new widget design guidelines strongly recommend 165 * that widget developers do not add extra padding to their widgets. This will help 166 * achieve consistency among widgets. 167 * 168 * Note: this method is only needed by developers of AppWidgetHosts. The method is provided in 169 * order for the AppWidgetHost to account for the automatic padding when computing the number 170 * of cells to allocate to a particular widget. 171 * 172 * @param context the current context 173 * @param component the component name of the widget 174 * @param padding Rect in which to place the output, if null, a new Rect will be allocated and 175 * returned 176 * @return default padding for this widget, in pixels 177 */ getDefaultPaddingForWidget(Context context, ComponentName component, Rect padding)178 public static Rect getDefaultPaddingForWidget(Context context, ComponentName component, 179 Rect padding) { 180 return getDefaultPaddingForWidget(context, padding); 181 } 182 getDefaultPaddingForWidget(Context context, Rect padding)183 private static Rect getDefaultPaddingForWidget(Context context, Rect padding) { 184 if (padding == null) { 185 padding = new Rect(0, 0, 0, 0); 186 } else { 187 padding.set(0, 0, 0, 0); 188 } 189 Resources r = context.getResources(); 190 padding.left = r.getDimensionPixelSize( 191 com.android.internal.R.dimen.default_app_widget_padding_left); 192 padding.right = r.getDimensionPixelSize( 193 com.android.internal.R.dimen.default_app_widget_padding_right); 194 padding.top = r.getDimensionPixelSize( 195 com.android.internal.R.dimen.default_app_widget_padding_top); 196 padding.bottom = r.getDimensionPixelSize( 197 com.android.internal.R.dimen.default_app_widget_padding_bottom); 198 return padding; 199 } 200 getDefaultPadding()201 private Rect getDefaultPadding() { 202 return getDefaultPaddingForWidget(mContext, null); 203 } 204 getAppWidgetId()205 public int getAppWidgetId() { 206 return mAppWidgetId; 207 } 208 getAppWidgetInfo()209 public AppWidgetProviderInfo getAppWidgetInfo() { 210 return mInfo; 211 } 212 213 @Override dispatchSaveInstanceState(SparseArray<Parcelable> container)214 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { 215 final SparseArray<Parcelable> jail = new SparseArray<>(); 216 super.dispatchSaveInstanceState(jail); 217 218 Bundle bundle = new Bundle(); 219 bundle.putSparseParcelableArray(KEY_JAILED_ARRAY, jail); 220 container.put(generateId(), bundle); 221 } 222 generateId()223 private int generateId() { 224 final int id = getId(); 225 return id == View.NO_ID ? mAppWidgetId : id; 226 } 227 228 @Override dispatchRestoreInstanceState(SparseArray<Parcelable> container)229 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 230 final Parcelable parcelable = container.get(generateId()); 231 232 SparseArray<Parcelable> jail = null; 233 if (parcelable instanceof Bundle) { 234 jail = ((Bundle) parcelable).getSparseParcelableArray(KEY_JAILED_ARRAY); 235 } 236 237 if (jail == null) jail = new SparseArray<>(); 238 239 try { 240 super.dispatchRestoreInstanceState(jail); 241 } catch (Exception e) { 242 Log.e(TAG, "failed to restoreInstanceState for widget id: " + mAppWidgetId + ", " 243 + (mInfo == null ? "null" : mInfo.provider), e); 244 } 245 } 246 247 @Override onLayout(boolean changed, int left, int top, int right, int bottom)248 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 249 try { 250 super.onLayout(changed, left, top, right, bottom); 251 } catch (final RuntimeException e) { 252 Log.e(TAG, "Remote provider threw runtime exception, using error view instead.", e); 253 removeViewInLayout(mView); 254 View child = getErrorView(); 255 prepareView(child); 256 addViewInLayout(child, 0, child.getLayoutParams()); 257 measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), 258 MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); 259 child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight, 260 child.getMeasuredHeight() + mPaddingTop + mPaddingBottom); 261 mView = child; 262 mViewMode = VIEW_MODE_ERROR; 263 } 264 } 265 266 /** 267 * Provide guidance about the size of this widget to the AppWidgetManager. The widths and 268 * heights should correspond to the full area the AppWidgetHostView is given. Padding added by 269 * the framework will be accounted for automatically. This information gets embedded into the 270 * AppWidget options and causes a callback to the AppWidgetProvider. 271 * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle) 272 * 273 * @param newOptions The bundle of options, in addition to the size information, 274 * can be null. 275 * @param minWidth The minimum width in dips that the widget will be displayed at. 276 * @param minHeight The maximum height in dips that the widget will be displayed at. 277 * @param maxWidth The maximum width in dips that the widget will be displayed at. 278 * @param maxHeight The maximum height in dips that the widget will be displayed at. 279 * 280 */ updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight)281 public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, 282 int maxHeight) { 283 updateAppWidgetSize(newOptions, minWidth, minHeight, maxWidth, maxHeight, false); 284 } 285 286 /** 287 * @hide 288 */ 289 @UnsupportedAppUsage updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight, boolean ignorePadding)290 public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, 291 int maxHeight, boolean ignorePadding) { 292 if (newOptions == null) { 293 newOptions = new Bundle(); 294 } 295 296 Rect padding = getDefaultPadding(); 297 float density = getResources().getDisplayMetrics().density; 298 299 int xPaddingDips = (int) ((padding.left + padding.right) / density); 300 int yPaddingDips = (int) ((padding.top + padding.bottom) / density); 301 302 int newMinWidth = minWidth - (ignorePadding ? 0 : xPaddingDips); 303 int newMinHeight = minHeight - (ignorePadding ? 0 : yPaddingDips); 304 int newMaxWidth = maxWidth - (ignorePadding ? 0 : xPaddingDips); 305 int newMaxHeight = maxHeight - (ignorePadding ? 0 : yPaddingDips); 306 307 AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext); 308 309 // We get the old options to see if the sizes have changed 310 Bundle oldOptions = widgetManager.getAppWidgetOptions(mAppWidgetId); 311 boolean needsUpdate = false; 312 if (newMinWidth != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) || 313 newMinHeight != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) || 314 newMaxWidth != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH) || 315 newMaxHeight != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)) { 316 needsUpdate = true; 317 } 318 319 if (needsUpdate) { 320 newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, newMinWidth); 321 newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, newMinHeight); 322 newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, newMaxWidth); 323 newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, newMaxHeight); 324 updateAppWidgetOptions(newOptions); 325 } 326 } 327 328 /** 329 * Specify some extra information for the widget provider. Causes a callback to the 330 * AppWidgetProvider. 331 * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle) 332 * 333 * @param options The bundle of options information. 334 */ updateAppWidgetOptions(Bundle options)335 public void updateAppWidgetOptions(Bundle options) { 336 AppWidgetManager.getInstance(mContext).updateAppWidgetOptions(mAppWidgetId, options); 337 } 338 339 /** {@inheritDoc} */ 340 @Override generateLayoutParams(AttributeSet attrs)341 public LayoutParams generateLayoutParams(AttributeSet attrs) { 342 // We're being asked to inflate parameters, probably by a LayoutInflater 343 // in a remote Context. To help resolve any remote references, we 344 // inflate through our last mRemoteContext when it exists. 345 final Context context = mRemoteContext != null ? mRemoteContext : mContext; 346 return new FrameLayout.LayoutParams(context, attrs); 347 } 348 349 /** 350 * Sets an executor which can be used for asynchronously inflating. CPU intensive tasks like 351 * view inflation or loading images will be performed on the executor. The updates will still 352 * be applied on the UI thread. 353 * 354 * @param executor the executor to use or null. 355 */ setExecutor(Executor executor)356 public void setExecutor(Executor executor) { 357 if (mLastExecutionSignal != null) { 358 mLastExecutionSignal.cancel(); 359 mLastExecutionSignal = null; 360 } 361 362 mAsyncExecutor = executor; 363 } 364 365 /** 366 * Sets whether the widget is being displayed on a light/white background and use an 367 * alternate UI if available. 368 * @see RemoteViews#setLightBackgroundLayoutId(int) 369 */ setOnLightBackground(boolean onLightBackground)370 public void setOnLightBackground(boolean onLightBackground) { 371 mOnLightBackground = onLightBackground; 372 } 373 374 /** 375 * Update the AppWidgetProviderInfo for this view, and reset it to the 376 * initial layout. 377 */ resetAppWidget(AppWidgetProviderInfo info)378 void resetAppWidget(AppWidgetProviderInfo info) { 379 setAppWidget(mAppWidgetId, info); 380 mViewMode = VIEW_MODE_NOINIT; 381 updateAppWidget(null); 382 } 383 384 /** 385 * Process a set of {@link RemoteViews} coming in as an update from the 386 * AppWidget provider. Will animate into these new views as needed 387 */ updateAppWidget(RemoteViews remoteViews)388 public void updateAppWidget(RemoteViews remoteViews) { 389 applyRemoteViews(remoteViews, true); 390 } 391 392 /** 393 * @hide 394 */ applyRemoteViews(RemoteViews remoteViews, boolean useAsyncIfPossible)395 protected void applyRemoteViews(RemoteViews remoteViews, boolean useAsyncIfPossible) { 396 boolean recycled = false; 397 View content = null; 398 Exception exception = null; 399 400 if (mLastExecutionSignal != null) { 401 mLastExecutionSignal.cancel(); 402 mLastExecutionSignal = null; 403 } 404 405 if (remoteViews == null) { 406 if (mViewMode == VIEW_MODE_DEFAULT) { 407 // We've already done this -- nothing to do. 408 return; 409 } 410 content = getDefaultView(); 411 mLayoutId = -1; 412 mViewMode = VIEW_MODE_DEFAULT; 413 } else { 414 if (mOnLightBackground) { 415 remoteViews = remoteViews.getDarkTextViews(); 416 } 417 418 if (mAsyncExecutor != null && useAsyncIfPossible) { 419 inflateAsync(remoteViews); 420 return; 421 } 422 // Prepare a local reference to the remote Context so we're ready to 423 // inflate any requested LayoutParams. 424 mRemoteContext = getRemoteContext(); 425 int layoutId = remoteViews.getLayoutId(); 426 // If our stale view has been prepared to match active, and the new 427 // layout matches, try recycling it 428 if (content == null && layoutId == mLayoutId) { 429 try { 430 remoteViews.reapply(mContext, mView, mOnClickHandler); 431 content = mView; 432 recycled = true; 433 if (LOGD) Log.d(TAG, "was able to recycle existing layout"); 434 } catch (RuntimeException e) { 435 exception = e; 436 } 437 } 438 439 // Try normal RemoteView inflation 440 if (content == null) { 441 try { 442 content = remoteViews.apply(mContext, this, mOnClickHandler); 443 if (LOGD) Log.d(TAG, "had to inflate new layout"); 444 } catch (RuntimeException e) { 445 exception = e; 446 } 447 } 448 449 mLayoutId = layoutId; 450 mViewMode = VIEW_MODE_CONTENT; 451 } 452 453 applyContent(content, recycled, exception); 454 } 455 applyContent(View content, boolean recycled, Exception exception)456 private void applyContent(View content, boolean recycled, Exception exception) { 457 if (content == null) { 458 if (mViewMode == VIEW_MODE_ERROR) { 459 // We've already done this -- nothing to do. 460 return ; 461 } 462 if (exception != null) { 463 Log.w(TAG, "Error inflating RemoteViews : " + exception.toString()); 464 } 465 content = getErrorView(); 466 mViewMode = VIEW_MODE_ERROR; 467 } 468 469 if (!recycled) { 470 prepareView(content); 471 addView(content); 472 } 473 474 if (mView != content) { 475 removeView(mView); 476 mView = content; 477 } 478 } 479 inflateAsync(RemoteViews remoteViews)480 private void inflateAsync(RemoteViews remoteViews) { 481 // Prepare a local reference to the remote Context so we're ready to 482 // inflate any requested LayoutParams. 483 mRemoteContext = getRemoteContext(); 484 int layoutId = remoteViews.getLayoutId(); 485 486 // If our stale view has been prepared to match active, and the new 487 // layout matches, try recycling it 488 if (layoutId == mLayoutId && mView != null) { 489 try { 490 mLastExecutionSignal = remoteViews.reapplyAsync(mContext, 491 mView, 492 mAsyncExecutor, 493 new ViewApplyListener(remoteViews, layoutId, true), 494 mOnClickHandler); 495 } catch (Exception e) { 496 // Reapply failed. Try apply 497 } 498 } 499 if (mLastExecutionSignal == null) { 500 mLastExecutionSignal = remoteViews.applyAsync(mContext, 501 this, 502 mAsyncExecutor, 503 new ViewApplyListener(remoteViews, layoutId, false), 504 mOnClickHandler); 505 } 506 } 507 508 private class ViewApplyListener implements RemoteViews.OnViewAppliedListener { 509 private final RemoteViews mViews; 510 private final boolean mIsReapply; 511 private final int mLayoutId; 512 ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply)513 public ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply) { 514 mViews = views; 515 mLayoutId = layoutId; 516 mIsReapply = isReapply; 517 } 518 519 @Override onViewApplied(View v)520 public void onViewApplied(View v) { 521 AppWidgetHostView.this.mLayoutId = mLayoutId; 522 mViewMode = VIEW_MODE_CONTENT; 523 524 applyContent(v, mIsReapply, null); 525 } 526 527 @Override onError(Exception e)528 public void onError(Exception e) { 529 if (mIsReapply) { 530 // Try a fresh replay 531 mLastExecutionSignal = mViews.applyAsync(mContext, 532 AppWidgetHostView.this, 533 mAsyncExecutor, 534 new ViewApplyListener(mViews, mLayoutId, false), 535 mOnClickHandler); 536 } else { 537 applyContent(null, false, e); 538 } 539 } 540 } 541 542 /** 543 * Process data-changed notifications for the specified view in the specified 544 * set of {@link RemoteViews} views. 545 */ viewDataChanged(int viewId)546 void viewDataChanged(int viewId) { 547 View v = findViewById(viewId); 548 if ((v != null) && (v instanceof AdapterView<?>)) { 549 AdapterView<?> adapterView = (AdapterView<?>) v; 550 Adapter adapter = adapterView.getAdapter(); 551 if (adapter instanceof BaseAdapter) { 552 BaseAdapter baseAdapter = (BaseAdapter) adapter; 553 baseAdapter.notifyDataSetChanged(); 554 } else if (adapter == null && adapterView instanceof RemoteAdapterConnectionCallback) { 555 // If the adapter is null, it may mean that the RemoteViewsAapter has not yet 556 // connected to its associated service, and hence the adapter hasn't been set. 557 // In this case, we need to defer the notify call until it has been set. 558 ((RemoteAdapterConnectionCallback) adapterView).deferNotifyDataSetChanged(); 559 } 560 } 561 } 562 563 /** 564 * Build a {@link Context} cloned into another package name, usually for the 565 * purposes of reading remote resources. 566 * @hide 567 */ getRemoteContext()568 protected Context getRemoteContext() { 569 try { 570 // Return if cloned successfully, otherwise default 571 return mContext.createApplicationContext( 572 mInfo.providerInfo.applicationInfo, 573 Context.CONTEXT_RESTRICTED); 574 } catch (NameNotFoundException e) { 575 Log.e(TAG, "Package name " + mInfo.providerInfo.packageName + " not found"); 576 return mContext; 577 } 578 } 579 580 /** 581 * Prepare the given view to be shown. This might include adjusting 582 * {@link FrameLayout.LayoutParams} before inserting. 583 */ prepareView(View view)584 protected void prepareView(View view) { 585 // Take requested dimensions from child, but apply default gravity. 586 FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams)view.getLayoutParams(); 587 if (requested == null) { 588 requested = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, 589 LayoutParams.MATCH_PARENT); 590 } 591 592 requested.gravity = Gravity.CENTER; 593 view.setLayoutParams(requested); 594 } 595 596 /** 597 * Inflate and return the default layout requested by AppWidget provider. 598 */ getDefaultView()599 protected View getDefaultView() { 600 if (LOGD) { 601 Log.d(TAG, "getDefaultView"); 602 } 603 View defaultView = null; 604 Exception exception = null; 605 606 try { 607 if (mInfo != null) { 608 Context theirContext = getRemoteContext(); 609 mRemoteContext = theirContext; 610 LayoutInflater inflater = (LayoutInflater) 611 theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 612 inflater = inflater.cloneInContext(theirContext); 613 inflater.setFilter(INFLATER_FILTER); 614 AppWidgetManager manager = AppWidgetManager.getInstance(mContext); 615 Bundle options = manager.getAppWidgetOptions(mAppWidgetId); 616 617 int layoutId = mInfo.initialLayout; 618 if (options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) { 619 int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY); 620 if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) { 621 int kgLayoutId = mInfo.initialKeyguardLayout; 622 // If a default keyguard layout is not specified, use the standard 623 // default layout. 624 layoutId = kgLayoutId == 0 ? layoutId : kgLayoutId; 625 } 626 } 627 defaultView = inflater.inflate(layoutId, this, false); 628 if (!(defaultView instanceof AdapterView)) { 629 // AdapterView does not support onClickListener 630 defaultView.setOnClickListener(this::onDefaultViewClicked); 631 } 632 } else { 633 Log.w(TAG, "can't inflate defaultView because mInfo is missing"); 634 } 635 } catch (RuntimeException e) { 636 exception = e; 637 } 638 639 if (exception != null) { 640 Log.w(TAG, "Error inflating AppWidget " + mInfo + ": " + exception.toString()); 641 } 642 643 if (defaultView == null) { 644 if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error"); 645 defaultView = getErrorView(); 646 } 647 648 return defaultView; 649 } 650 onDefaultViewClicked(View view)651 private void onDefaultViewClicked(View view) { 652 if (mInfo != null) { 653 LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class); 654 List<LauncherActivityInfo> activities = launcherApps.getActivityList( 655 mInfo.provider.getPackageName(), mInfo.getProfile()); 656 if (!activities.isEmpty()) { 657 LauncherActivityInfo ai = activities.get(0); 658 launcherApps.startMainActivity(ai.getComponentName(), ai.getUser(), 659 RemoteViews.getSourceBounds(view), null); 660 } 661 } 662 } 663 664 /** 665 * Inflate and return a view that represents an error state. 666 */ getErrorView()667 protected View getErrorView() { 668 TextView tv = new TextView(mContext); 669 tv.setText(com.android.internal.R.string.gadget_host_error_inflating); 670 // TODO: get this color from somewhere. 671 tv.setBackgroundColor(Color.argb(127, 0, 0, 0)); 672 return tv; 673 } 674 675 /** @hide */ 676 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)677 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 678 super.onInitializeAccessibilityNodeInfoInternal(info); 679 info.setClassName(AppWidgetHostView.class.getName()); 680 } 681 682 /** @hide */ createSharedElementActivityOptions( int[] sharedViewIds, String[] sharedViewNames, Intent fillInIntent)683 public ActivityOptions createSharedElementActivityOptions( 684 int[] sharedViewIds, String[] sharedViewNames, Intent fillInIntent) { 685 Context parentContext = getContext(); 686 while ((parentContext instanceof ContextWrapper) 687 && !(parentContext instanceof Activity)) { 688 parentContext = ((ContextWrapper) parentContext).getBaseContext(); 689 } 690 if (!(parentContext instanceof Activity)) { 691 return null; 692 } 693 694 List<Pair<View, String>> sharedElements = new ArrayList<>(); 695 Bundle extras = new Bundle(); 696 697 for (int i = 0; i < sharedViewIds.length; i++) { 698 View view = findViewById(sharedViewIds[i]); 699 if (view != null) { 700 sharedElements.add(Pair.create(view, sharedViewNames[i])); 701 702 extras.putParcelable(sharedViewNames[i], RemoteViews.getSourceBounds(view)); 703 } 704 } 705 706 if (!sharedElements.isEmpty()) { 707 fillInIntent.putExtra(RemoteViews.EXTRA_SHARED_ELEMENT_BOUNDS, extras); 708 final ActivityOptions opts = ActivityOptions.makeSceneTransitionAnimation( 709 (Activity) parentContext, 710 sharedElements.toArray(new Pair[sharedElements.size()])); 711 opts.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 712 return opts; 713 } 714 return null; 715 } 716 getHandler(OnClickHandler handler)717 private OnClickHandler getHandler(OnClickHandler handler) { 718 return (view, pendingIntent, response) -> { 719 AppWidgetManager.getInstance(mContext).noteAppWidgetTapped(mAppWidgetId); 720 if (handler != null) { 721 return handler.onClickHandler(view, pendingIntent, response); 722 } else { 723 return RemoteViews.startPendingIntent(view, pendingIntent, 724 response.getLaunchOptions(view)); 725 } 726 }; 727 } 728 } 729