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 = 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 = 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 427 // If our stale view has been prepared to match active, and the new 428 // layout matches, try recycling it 429 if (content == null && layoutId == mLayoutId) { 430 try { 431 remoteViews.reapply(mContext, mView, mOnClickHandler); 432 content = mView; 433 recycled = true; 434 if (LOGD) Log.d(TAG, "was able to recycle existing layout"); 435 } catch (RuntimeException e) { 436 exception = e; 437 } 438 } 439 440 // Try normal RemoteView inflation 441 if (content == null) { 442 try { 443 content = remoteViews.apply(mContext, this, mOnClickHandler); 444 if (LOGD) Log.d(TAG, "had to inflate new layout"); 445 } catch (RuntimeException e) { 446 exception = e; 447 } 448 } 449 450 mLayoutId = layoutId; 451 mViewMode = VIEW_MODE_CONTENT; 452 } 453 454 applyContent(content, recycled, exception); 455 } 456 applyContent(View content, boolean recycled, Exception exception)457 private void applyContent(View content, boolean recycled, Exception exception) { 458 if (content == null) { 459 if (mViewMode == VIEW_MODE_ERROR) { 460 // We've already done this -- nothing to do. 461 return ; 462 } 463 if (exception != null) { 464 Log.w(TAG, "Error inflating RemoteViews : " + exception.toString()); 465 } 466 content = getErrorView(); 467 mViewMode = VIEW_MODE_ERROR; 468 } 469 470 if (!recycled) { 471 prepareView(content); 472 addView(content); 473 } 474 475 if (mView != content) { 476 removeView(mView); 477 mView = content; 478 } 479 } 480 inflateAsync(RemoteViews remoteViews)481 private void inflateAsync(RemoteViews remoteViews) { 482 // Prepare a local reference to the remote Context so we're ready to 483 // inflate any requested LayoutParams. 484 mRemoteContext = getRemoteContext(); 485 int layoutId = remoteViews.getLayoutId(); 486 487 // If our stale view has been prepared to match active, and the new 488 // layout matches, try recycling it 489 if (layoutId == mLayoutId && mView != null) { 490 try { 491 mLastExecutionSignal = remoteViews.reapplyAsync(mContext, 492 mView, 493 mAsyncExecutor, 494 new ViewApplyListener(remoteViews, layoutId, true), 495 mOnClickHandler); 496 } catch (Exception e) { 497 // Reapply failed. Try apply 498 } 499 } 500 if (mLastExecutionSignal == null) { 501 mLastExecutionSignal = remoteViews.applyAsync(mContext, 502 this, 503 mAsyncExecutor, 504 new ViewApplyListener(remoteViews, layoutId, false), 505 mOnClickHandler); 506 } 507 } 508 509 private class ViewApplyListener implements RemoteViews.OnViewAppliedListener { 510 private final RemoteViews mViews; 511 private final boolean mIsReapply; 512 private final int mLayoutId; 513 ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply)514 public ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply) { 515 mViews = views; 516 mLayoutId = layoutId; 517 mIsReapply = isReapply; 518 } 519 520 @Override onViewApplied(View v)521 public void onViewApplied(View v) { 522 AppWidgetHostView.this.mLayoutId = mLayoutId; 523 mViewMode = VIEW_MODE_CONTENT; 524 525 applyContent(v, mIsReapply, null); 526 } 527 528 @Override onError(Exception e)529 public void onError(Exception e) { 530 if (mIsReapply) { 531 // Try a fresh replay 532 mLastExecutionSignal = mViews.applyAsync(mContext, 533 AppWidgetHostView.this, 534 mAsyncExecutor, 535 new ViewApplyListener(mViews, mLayoutId, false), 536 mOnClickHandler); 537 } else { 538 applyContent(null, false, e); 539 } 540 } 541 } 542 543 /** 544 * Process data-changed notifications for the specified view in the specified 545 * set of {@link RemoteViews} views. 546 */ viewDataChanged(int viewId)547 void viewDataChanged(int viewId) { 548 View v = findViewById(viewId); 549 if ((v != null) && (v instanceof AdapterView<?>)) { 550 AdapterView<?> adapterView = (AdapterView<?>) v; 551 Adapter adapter = adapterView.getAdapter(); 552 if (adapter instanceof BaseAdapter) { 553 BaseAdapter baseAdapter = (BaseAdapter) adapter; 554 baseAdapter.notifyDataSetChanged(); 555 } else if (adapter == null && adapterView instanceof RemoteAdapterConnectionCallback) { 556 // If the adapter is null, it may mean that the RemoteViewsAapter has not yet 557 // connected to its associated service, and hence the adapter hasn't been set. 558 // In this case, we need to defer the notify call until it has been set. 559 ((RemoteAdapterConnectionCallback) adapterView).deferNotifyDataSetChanged(); 560 } 561 } 562 } 563 564 /** 565 * Build a {@link Context} cloned into another package name, usually for the 566 * purposes of reading remote resources. 567 * @hide 568 */ getRemoteContext()569 protected Context getRemoteContext() { 570 try { 571 // Return if cloned successfully, otherwise default 572 return mContext.createApplicationContext( 573 mInfo.providerInfo.applicationInfo, 574 Context.CONTEXT_RESTRICTED); 575 } catch (NameNotFoundException e) { 576 Log.e(TAG, "Package name " + mInfo.providerInfo.packageName + " not found"); 577 return mContext; 578 } 579 } 580 581 /** 582 * Prepare the given view to be shown. This might include adjusting 583 * {@link FrameLayout.LayoutParams} before inserting. 584 */ prepareView(View view)585 protected void prepareView(View view) { 586 // Take requested dimensions from child, but apply default gravity. 587 FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams)view.getLayoutParams(); 588 if (requested == null) { 589 requested = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, 590 LayoutParams.MATCH_PARENT); 591 } 592 593 requested.gravity = Gravity.CENTER; 594 view.setLayoutParams(requested); 595 } 596 597 /** 598 * Inflate and return the default layout requested by AppWidget provider. 599 */ getDefaultView()600 protected View getDefaultView() { 601 if (LOGD) { 602 Log.d(TAG, "getDefaultView"); 603 } 604 View defaultView = null; 605 Exception exception = null; 606 607 try { 608 if (mInfo != null) { 609 Context theirContext = getRemoteContext(); 610 mRemoteContext = theirContext; 611 LayoutInflater inflater = (LayoutInflater) 612 theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 613 inflater = inflater.cloneInContext(theirContext); 614 inflater.setFilter(INFLATER_FILTER); 615 AppWidgetManager manager = AppWidgetManager.getInstance(mContext); 616 Bundle options = manager.getAppWidgetOptions(mAppWidgetId); 617 618 int layoutId = mInfo.initialLayout; 619 if (options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) { 620 int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY); 621 if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) { 622 int kgLayoutId = mInfo.initialKeyguardLayout; 623 // If a default keyguard layout is not specified, use the standard 624 // default layout. 625 layoutId = kgLayoutId == 0 ? layoutId : kgLayoutId; 626 } 627 } 628 defaultView = inflater.inflate(layoutId, this, false); 629 defaultView.setOnClickListener(this::onDefaultViewClicked); 630 } else { 631 Log.w(TAG, "can't inflate defaultView because mInfo is missing"); 632 } 633 } catch (RuntimeException e) { 634 exception = e; 635 } 636 637 if (exception != null) { 638 Log.w(TAG, "Error inflating AppWidget " + mInfo + ": " + exception.toString()); 639 } 640 641 if (defaultView == null) { 642 if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error"); 643 defaultView = getErrorView(); 644 } 645 646 return defaultView; 647 } 648 onDefaultViewClicked(View view)649 private void onDefaultViewClicked(View view) { 650 if (mInfo != null) { 651 LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class); 652 List<LauncherActivityInfo> activities = launcherApps.getActivityList( 653 mInfo.provider.getPackageName(), mInfo.getProfile()); 654 if (!activities.isEmpty()) { 655 LauncherActivityInfo ai = activities.get(0); 656 launcherApps.startMainActivity(ai.getComponentName(), ai.getUser(), 657 RemoteViews.getSourceBounds(view), null); 658 } 659 } 660 } 661 662 /** 663 * Inflate and return a view that represents an error state. 664 */ getErrorView()665 protected View getErrorView() { 666 TextView tv = new TextView(mContext); 667 tv.setText(com.android.internal.R.string.gadget_host_error_inflating); 668 // TODO: get this color from somewhere. 669 tv.setBackgroundColor(Color.argb(127, 0, 0, 0)); 670 return tv; 671 } 672 673 /** @hide */ 674 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)675 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 676 super.onInitializeAccessibilityNodeInfoInternal(info); 677 info.setClassName(AppWidgetHostView.class.getName()); 678 } 679 680 /** @hide */ createSharedElementActivityOptions( int[] sharedViewIds, String[] sharedViewNames, Intent fillInIntent)681 public ActivityOptions createSharedElementActivityOptions( 682 int[] sharedViewIds, String[] sharedViewNames, Intent fillInIntent) { 683 Context parentContext = getContext(); 684 while ((parentContext instanceof ContextWrapper) 685 && !(parentContext instanceof Activity)) { 686 parentContext = ((ContextWrapper) parentContext).getBaseContext(); 687 } 688 if (!(parentContext instanceof Activity)) { 689 return null; 690 } 691 692 List<Pair<View, String>> sharedElements = new ArrayList<>(); 693 Bundle extras = new Bundle(); 694 695 for (int i = 0; i < sharedViewIds.length; i++) { 696 View view = findViewById(sharedViewIds[i]); 697 if (view != null) { 698 sharedElements.add(Pair.create(view, sharedViewNames[i])); 699 700 extras.putParcelable(sharedViewNames[i], RemoteViews.getSourceBounds(view)); 701 } 702 } 703 704 if (!sharedElements.isEmpty()) { 705 fillInIntent.putExtra(RemoteViews.EXTRA_SHARED_ELEMENT_BOUNDS, extras); 706 final ActivityOptions opts = ActivityOptions.makeSceneTransitionAnimation( 707 (Activity) parentContext, 708 sharedElements.toArray(new Pair[sharedElements.size()])); 709 opts.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 710 return opts; 711 } 712 return null; 713 } 714 } 715