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.content.ComponentName; 20 import android.content.Context; 21 import android.content.pm.ApplicationInfo; 22 import android.content.pm.LauncherApps; 23 import android.content.pm.PackageManager; 24 import android.content.pm.PackageManager.NameNotFoundException; 25 import android.content.res.Resources; 26 import android.graphics.Bitmap; 27 import android.graphics.Canvas; 28 import android.graphics.Color; 29 import android.graphics.Paint; 30 import android.graphics.Rect; 31 import android.os.Build; 32 import android.os.Bundle; 33 import android.os.CancellationSignal; 34 import android.os.Parcel; 35 import android.os.Parcelable; 36 import android.os.SystemClock; 37 import android.util.AttributeSet; 38 import android.util.Log; 39 import android.util.SparseArray; 40 import android.view.Gravity; 41 import android.view.LayoutInflater; 42 import android.view.View; 43 import android.view.ViewGroup; 44 import android.view.accessibility.AccessibilityNodeInfo; 45 import android.widget.Adapter; 46 import android.widget.AdapterView; 47 import android.widget.BaseAdapter; 48 import android.widget.FrameLayout; 49 import android.widget.RemoteViews; 50 import android.widget.RemoteViews.OnClickHandler; 51 import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback; 52 import android.widget.TextView; 53 54 import java.util.concurrent.Executor; 55 56 /** 57 * Provides the glue to show AppWidget views. This class offers automatic animation 58 * between updates, and will try recycling old views for each incoming 59 * {@link RemoteViews}. 60 */ 61 public class AppWidgetHostView extends FrameLayout { 62 static final String TAG = "AppWidgetHostView"; 63 static final boolean LOGD = false; 64 static final boolean CROSSFADE = false; 65 66 static final int VIEW_MODE_NOINIT = 0; 67 static final int VIEW_MODE_CONTENT = 1; 68 static final int VIEW_MODE_ERROR = 2; 69 static final int VIEW_MODE_DEFAULT = 3; 70 71 static final int FADE_DURATION = 1000; 72 73 // When we're inflating the initialLayout for a AppWidget, we only allow 74 // views that are allowed in RemoteViews. 75 static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() { 76 public boolean onLoadClass(Class clazz) { 77 return clazz.isAnnotationPresent(RemoteViews.RemoteView.class); 78 } 79 }; 80 81 Context mContext; 82 Context mRemoteContext; 83 84 int mAppWidgetId; 85 AppWidgetProviderInfo mInfo; 86 View mView; 87 int mViewMode = VIEW_MODE_NOINIT; 88 int mLayoutId = -1; 89 long mFadeStartTime = -1; 90 Bitmap mOld; 91 Paint mOldPaint = new Paint(); 92 private OnClickHandler mOnClickHandler; 93 94 private Executor mAsyncExecutor; 95 private CancellationSignal mLastExecutionSignal; 96 97 /** 98 * Create a host view. Uses default fade animations. 99 */ AppWidgetHostView(Context context)100 public AppWidgetHostView(Context context) { 101 this(context, android.R.anim.fade_in, android.R.anim.fade_out); 102 } 103 104 /** 105 * @hide 106 */ AppWidgetHostView(Context context, OnClickHandler handler)107 public AppWidgetHostView(Context context, OnClickHandler handler) { 108 this(context, android.R.anim.fade_in, android.R.anim.fade_out); 109 mOnClickHandler = handler; 110 } 111 112 /** 113 * Create a host view. Uses specified animations when pushing 114 * {@link #updateAppWidget(RemoteViews)}. 115 * 116 * @param animationIn Resource ID of in animation to use 117 * @param animationOut Resource ID of out animation to use 118 */ 119 @SuppressWarnings({"UnusedDeclaration"}) AppWidgetHostView(Context context, int animationIn, int animationOut)120 public AppWidgetHostView(Context context, int animationIn, int animationOut) { 121 super(context); 122 mContext = context; 123 // We want to segregate the view ids within AppWidgets to prevent 124 // problems when those ids collide with view ids in the AppWidgetHost. 125 setIsRootNamespace(true); 126 } 127 128 /** 129 * Pass the given handler to RemoteViews when updating this widget. Unless this 130 * is done immediatly after construction, a call to {@link #updateAppWidget(RemoteViews)} 131 * should be made. 132 * @param handler 133 * @hide 134 */ setOnClickHandler(OnClickHandler handler)135 public void setOnClickHandler(OnClickHandler handler) { 136 mOnClickHandler = handler; 137 } 138 139 /** 140 * Set the AppWidget that will be displayed by this view. This method also adds default padding 141 * to widgets, as described in {@link #getDefaultPaddingForWidget(Context, ComponentName, Rect)} 142 * and can be overridden in order to add custom padding. 143 */ setAppWidget(int appWidgetId, AppWidgetProviderInfo info)144 public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) { 145 mAppWidgetId = appWidgetId; 146 mInfo = info; 147 148 // Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for 149 // a widget, eg. for some widgets in safe mode. 150 if (info != null) { 151 // We add padding to the AppWidgetHostView if necessary 152 Rect padding = getDefaultPaddingForWidget(mContext, info.provider, null); 153 setPadding(padding.left, padding.top, padding.right, padding.bottom); 154 updateContentDescription(info); 155 } 156 } 157 158 /** 159 * As of ICE_CREAM_SANDWICH we are automatically adding padding to widgets targeting 160 * ICE_CREAM_SANDWICH and higher. The new widget design guidelines strongly recommend 161 * that widget developers do not add extra padding to their widgets. This will help 162 * achieve consistency among widgets. 163 * 164 * Note: this method is only needed by developers of AppWidgetHosts. The method is provided in 165 * order for the AppWidgetHost to account for the automatic padding when computing the number 166 * of cells to allocate to a particular widget. 167 * 168 * @param context the current context 169 * @param component the component name of the widget 170 * @param padding Rect in which to place the output, if null, a new Rect will be allocated and 171 * returned 172 * @return default padding for this widget, in pixels 173 */ getDefaultPaddingForWidget(Context context, ComponentName component, Rect padding)174 public static Rect getDefaultPaddingForWidget(Context context, ComponentName component, 175 Rect padding) { 176 PackageManager packageManager = context.getPackageManager(); 177 ApplicationInfo appInfo; 178 179 if (padding == null) { 180 padding = new Rect(0, 0, 0, 0); 181 } else { 182 padding.set(0, 0, 0, 0); 183 } 184 185 try { 186 appInfo = packageManager.getApplicationInfo(component.getPackageName(), 0); 187 } catch (NameNotFoundException e) { 188 // if we can't find the package, return 0 padding 189 return padding; 190 } 191 192 if (appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 193 Resources r = context.getResources(); 194 padding.left = r.getDimensionPixelSize(com.android.internal. 195 R.dimen.default_app_widget_padding_left); 196 padding.right = r.getDimensionPixelSize(com.android.internal. 197 R.dimen.default_app_widget_padding_right); 198 padding.top = r.getDimensionPixelSize(com.android.internal. 199 R.dimen.default_app_widget_padding_top); 200 padding.bottom = r.getDimensionPixelSize(com.android.internal. 201 R.dimen.default_app_widget_padding_bottom); 202 } 203 return padding; 204 } 205 getAppWidgetId()206 public int getAppWidgetId() { 207 return mAppWidgetId; 208 } 209 getAppWidgetInfo()210 public AppWidgetProviderInfo getAppWidgetInfo() { 211 return mInfo; 212 } 213 214 @Override dispatchSaveInstanceState(SparseArray<Parcelable> container)215 protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { 216 final ParcelableSparseArray jail = new ParcelableSparseArray(); 217 super.dispatchSaveInstanceState(jail); 218 container.put(generateId(), jail); 219 } 220 generateId()221 private int generateId() { 222 final int id = getId(); 223 return id == View.NO_ID ? mAppWidgetId : id; 224 } 225 226 @Override dispatchRestoreInstanceState(SparseArray<Parcelable> container)227 protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 228 final Parcelable parcelable = container.get(generateId()); 229 230 ParcelableSparseArray jail = null; 231 if (parcelable != null && parcelable instanceof ParcelableSparseArray) { 232 jail = (ParcelableSparseArray) parcelable; 233 } 234 235 if (jail == null) jail = new ParcelableSparseArray(); 236 237 try { 238 super.dispatchRestoreInstanceState(jail); 239 } catch (Exception e) { 240 Log.e(TAG, "failed to restoreInstanceState for widget id: " + mAppWidgetId + ", " 241 + (mInfo == null ? "null" : mInfo.provider), e); 242 } 243 } 244 245 @Override onLayout(boolean changed, int left, int top, int right, int bottom)246 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 247 try { 248 super.onLayout(changed, left, top, right, bottom); 249 } catch (final RuntimeException e) { 250 Log.e(TAG, "Remote provider threw runtime exception, using error view instead.", e); 251 removeViewInLayout(mView); 252 View child = getErrorView(); 253 prepareView(child); 254 addViewInLayout(child, 0, child.getLayoutParams()); 255 measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), 256 MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); 257 child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight, 258 child.getMeasuredHeight() + mPaddingTop + mPaddingBottom); 259 mView = child; 260 mViewMode = VIEW_MODE_ERROR; 261 } 262 } 263 264 /** 265 * Provide guidance about the size of this widget to the AppWidgetManager. The widths and 266 * heights should correspond to the full area the AppWidgetHostView is given. Padding added by 267 * the framework will be accounted for automatically. This information gets embedded into the 268 * AppWidget options and causes a callback to the AppWidgetProvider. 269 * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle) 270 * 271 * @param newOptions The bundle of options, in addition to the size information, 272 * can be null. 273 * @param minWidth The minimum width in dips that the widget will be displayed at. 274 * @param minHeight The maximum height in dips that the widget will be displayed at. 275 * @param maxWidth The maximum width in dips that the widget will be displayed at. 276 * @param maxHeight The maximum height in dips that the widget will be displayed at. 277 * 278 */ updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight)279 public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, 280 int maxHeight) { 281 updateAppWidgetSize(newOptions, minWidth, minHeight, maxWidth, maxHeight, false); 282 } 283 284 /** 285 * @hide 286 */ updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight, boolean ignorePadding)287 public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, 288 int maxHeight, boolean ignorePadding) { 289 if (newOptions == null) { 290 newOptions = new Bundle(); 291 } 292 293 Rect padding = new Rect(); 294 if (mInfo != null) { 295 padding = getDefaultPaddingForWidget(mContext, mInfo.provider, padding); 296 } 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 * Update the AppWidgetProviderInfo for this view, and reset it to the 367 * initial layout. 368 */ resetAppWidget(AppWidgetProviderInfo info)369 void resetAppWidget(AppWidgetProviderInfo info) { 370 mInfo = info; 371 mViewMode = VIEW_MODE_NOINIT; 372 updateAppWidget(null); 373 } 374 375 /** 376 * Process a set of {@link RemoteViews} coming in as an update from the 377 * AppWidget provider. Will animate into these new views as needed 378 */ updateAppWidget(RemoteViews remoteViews)379 public void updateAppWidget(RemoteViews remoteViews) { 380 applyRemoteViews(remoteViews, true); 381 } 382 383 /** 384 * @hide 385 */ applyRemoteViews(RemoteViews remoteViews, boolean useAsyncIfPossible)386 protected void applyRemoteViews(RemoteViews remoteViews, boolean useAsyncIfPossible) { 387 if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld); 388 389 boolean recycled = false; 390 View content = null; 391 Exception exception = null; 392 393 // Capture the old view into a bitmap so we can do the crossfade. 394 if (CROSSFADE) { 395 if (mFadeStartTime < 0) { 396 if (mView != null) { 397 final int width = mView.getWidth(); 398 final int height = mView.getHeight(); 399 try { 400 mOld = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 401 } catch (OutOfMemoryError e) { 402 // we just won't do the fade 403 mOld = null; 404 } 405 if (mOld != null) { 406 //mView.drawIntoBitmap(mOld); 407 } 408 } 409 } 410 } 411 412 if (mLastExecutionSignal != null) { 413 mLastExecutionSignal.cancel(); 414 mLastExecutionSignal = null; 415 } 416 417 if (remoteViews == null) { 418 if (mViewMode == VIEW_MODE_DEFAULT) { 419 // We've already done this -- nothing to do. 420 return; 421 } 422 content = getDefaultView(); 423 mLayoutId = -1; 424 mViewMode = VIEW_MODE_DEFAULT; 425 } else { 426 if (mAsyncExecutor != null && useAsyncIfPossible) { 427 inflateAsync(remoteViews); 428 return; 429 } 430 // Prepare a local reference to the remote Context so we're ready to 431 // inflate any requested LayoutParams. 432 mRemoteContext = getRemoteContext(); 433 int layoutId = remoteViews.getLayoutId(); 434 435 // If our stale view has been prepared to match active, and the new 436 // layout matches, try recycling it 437 if (content == null && layoutId == mLayoutId) { 438 try { 439 remoteViews.reapply(mContext, mView, mOnClickHandler); 440 content = mView; 441 recycled = true; 442 if (LOGD) Log.d(TAG, "was able to recycle existing layout"); 443 } catch (RuntimeException e) { 444 exception = e; 445 } 446 } 447 448 // Try normal RemoteView inflation 449 if (content == null) { 450 try { 451 content = remoteViews.apply(mContext, this, mOnClickHandler); 452 if (LOGD) Log.d(TAG, "had to inflate new layout"); 453 } catch (RuntimeException e) { 454 exception = e; 455 } 456 } 457 458 mLayoutId = layoutId; 459 mViewMode = VIEW_MODE_CONTENT; 460 } 461 462 applyContent(content, recycled, exception); 463 updateContentDescription(mInfo); 464 } 465 applyContent(View content, boolean recycled, Exception exception)466 private void applyContent(View content, boolean recycled, Exception exception) { 467 if (content == null) { 468 if (mViewMode == VIEW_MODE_ERROR) { 469 // We've already done this -- nothing to do. 470 return ; 471 } 472 Log.w(TAG, "updateAppWidget couldn't find any view, using error view", exception); 473 content = getErrorView(); 474 mViewMode = VIEW_MODE_ERROR; 475 } 476 477 if (!recycled) { 478 prepareView(content); 479 addView(content); 480 } 481 482 if (mView != content) { 483 removeView(mView); 484 mView = content; 485 } 486 487 if (CROSSFADE) { 488 if (mFadeStartTime < 0) { 489 // if there is already an animation in progress, don't do anything -- 490 // the new view will pop in on top of the old one during the cross fade, 491 // and that looks okay. 492 mFadeStartTime = SystemClock.uptimeMillis(); 493 invalidate(); 494 } 495 } 496 } 497 updateContentDescription(AppWidgetProviderInfo info)498 private void updateContentDescription(AppWidgetProviderInfo info) { 499 if (info != null) { 500 LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class); 501 ApplicationInfo appInfo = null; 502 try { 503 appInfo = launcherApps.getApplicationInfo( 504 info.provider.getPackageName(), 0, info.getProfile()); 505 } catch (NameNotFoundException e) { 506 // ignore -- use null. 507 } 508 if (appInfo != null && 509 (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) { 510 setContentDescription( 511 Resources.getSystem().getString( 512 com.android.internal.R.string.suspended_widget_accessibility, info.label)); 513 } else { 514 setContentDescription(info.label); 515 } 516 } 517 } 518 inflateAsync(RemoteViews remoteViews)519 private void inflateAsync(RemoteViews remoteViews) { 520 // Prepare a local reference to the remote Context so we're ready to 521 // inflate any requested LayoutParams. 522 mRemoteContext = getRemoteContext(); 523 int layoutId = remoteViews.getLayoutId(); 524 525 // If our stale view has been prepared to match active, and the new 526 // layout matches, try recycling it 527 if (layoutId == mLayoutId && mView != null) { 528 try { 529 mLastExecutionSignal = remoteViews.reapplyAsync(mContext, 530 mView, 531 mAsyncExecutor, 532 new ViewApplyListener(remoteViews, layoutId, true), 533 mOnClickHandler); 534 } catch (Exception e) { 535 // Reapply failed. Try apply 536 } 537 } 538 if (mLastExecutionSignal == null) { 539 mLastExecutionSignal = remoteViews.applyAsync(mContext, 540 this, 541 mAsyncExecutor, 542 new ViewApplyListener(remoteViews, layoutId, false), 543 mOnClickHandler); 544 } 545 } 546 547 private class ViewApplyListener implements RemoteViews.OnViewAppliedListener { 548 private final RemoteViews mViews; 549 private final boolean mIsReapply; 550 private final int mLayoutId; 551 ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply)552 public ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply) { 553 mViews = views; 554 mLayoutId = layoutId; 555 mIsReapply = isReapply; 556 } 557 558 @Override onViewApplied(View v)559 public void onViewApplied(View v) { 560 AppWidgetHostView.this.mLayoutId = mLayoutId; 561 mViewMode = VIEW_MODE_CONTENT; 562 563 applyContent(v, mIsReapply, null); 564 } 565 566 @Override onError(Exception e)567 public void onError(Exception e) { 568 if (mIsReapply) { 569 // Try a fresh replay 570 mLastExecutionSignal = mViews.applyAsync(mContext, 571 AppWidgetHostView.this, 572 mAsyncExecutor, 573 new ViewApplyListener(mViews, mLayoutId, false), 574 mOnClickHandler); 575 } else { 576 applyContent(null, false, e); 577 } 578 } 579 } 580 581 /** 582 * Process data-changed notifications for the specified view in the specified 583 * set of {@link RemoteViews} views. 584 */ viewDataChanged(int viewId)585 void viewDataChanged(int viewId) { 586 View v = findViewById(viewId); 587 if ((v != null) && (v instanceof AdapterView<?>)) { 588 AdapterView<?> adapterView = (AdapterView<?>) v; 589 Adapter adapter = adapterView.getAdapter(); 590 if (adapter instanceof BaseAdapter) { 591 BaseAdapter baseAdapter = (BaseAdapter) adapter; 592 baseAdapter.notifyDataSetChanged(); 593 } else if (adapter == null && adapterView instanceof RemoteAdapterConnectionCallback) { 594 // If the adapter is null, it may mean that the RemoteViewsAapter has not yet 595 // connected to its associated service, and hence the adapter hasn't been set. 596 // In this case, we need to defer the notify call until it has been set. 597 ((RemoteAdapterConnectionCallback) adapterView).deferNotifyDataSetChanged(); 598 } 599 } 600 } 601 602 /** 603 * Build a {@link Context} cloned into another package name, usually for the 604 * purposes of reading remote resources. 605 * @hide 606 */ getRemoteContext()607 protected Context getRemoteContext() { 608 try { 609 // Return if cloned successfully, otherwise default 610 return mContext.createApplicationContext( 611 mInfo.providerInfo.applicationInfo, 612 Context.CONTEXT_RESTRICTED); 613 } catch (NameNotFoundException e) { 614 Log.e(TAG, "Package name " + mInfo.providerInfo.packageName + " not found"); 615 return mContext; 616 } 617 } 618 619 @Override drawChild(Canvas canvas, View child, long drawingTime)620 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 621 if (CROSSFADE) { 622 int alpha; 623 int l = child.getLeft(); 624 int t = child.getTop(); 625 if (mFadeStartTime > 0) { 626 alpha = (int)(((drawingTime-mFadeStartTime)*255)/FADE_DURATION); 627 if (alpha > 255) { 628 alpha = 255; 629 } 630 Log.d(TAG, "drawChild alpha=" + alpha + " l=" + l + " t=" + t 631 + " w=" + child.getWidth()); 632 if (alpha != 255 && mOld != null) { 633 mOldPaint.setAlpha(255-alpha); 634 //canvas.drawBitmap(mOld, l, t, mOldPaint); 635 } 636 } else { 637 alpha = 255; 638 } 639 int restoreTo = canvas.saveLayerAlpha(l, t, child.getWidth(), child.getHeight(), alpha, 640 Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); 641 boolean rv = super.drawChild(canvas, child, drawingTime); 642 canvas.restoreToCount(restoreTo); 643 if (alpha < 255) { 644 invalidate(); 645 } else { 646 mFadeStartTime = -1; 647 if (mOld != null) { 648 mOld.recycle(); 649 mOld = null; 650 } 651 } 652 return rv; 653 } else { 654 return super.drawChild(canvas, child, drawingTime); 655 } 656 } 657 658 /** 659 * Prepare the given view to be shown. This might include adjusting 660 * {@link FrameLayout.LayoutParams} before inserting. 661 */ prepareView(View view)662 protected void prepareView(View view) { 663 // Take requested dimensions from child, but apply default gravity. 664 FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams)view.getLayoutParams(); 665 if (requested == null) { 666 requested = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, 667 LayoutParams.MATCH_PARENT); 668 } 669 670 requested.gravity = Gravity.CENTER; 671 view.setLayoutParams(requested); 672 } 673 674 /** 675 * Inflate and return the default layout requested by AppWidget provider. 676 */ getDefaultView()677 protected View getDefaultView() { 678 if (LOGD) { 679 Log.d(TAG, "getDefaultView"); 680 } 681 View defaultView = null; 682 Exception exception = null; 683 684 try { 685 if (mInfo != null) { 686 Context theirContext = getRemoteContext(); 687 mRemoteContext = theirContext; 688 LayoutInflater inflater = (LayoutInflater) 689 theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 690 inflater = inflater.cloneInContext(theirContext); 691 inflater.setFilter(sInflaterFilter); 692 AppWidgetManager manager = AppWidgetManager.getInstance(mContext); 693 Bundle options = manager.getAppWidgetOptions(mAppWidgetId); 694 695 int layoutId = mInfo.initialLayout; 696 if (options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) { 697 int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY); 698 if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) { 699 int kgLayoutId = mInfo.initialKeyguardLayout; 700 // If a default keyguard layout is not specified, use the standard 701 // default layout. 702 layoutId = kgLayoutId == 0 ? layoutId : kgLayoutId; 703 } 704 } 705 defaultView = inflater.inflate(layoutId, this, false); 706 } else { 707 Log.w(TAG, "can't inflate defaultView because mInfo is missing"); 708 } 709 } catch (RuntimeException e) { 710 exception = e; 711 } 712 713 if (exception != null) { 714 Log.w(TAG, "Error inflating AppWidget " + mInfo + ": " + exception.toString()); 715 } 716 717 if (defaultView == null) { 718 if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error"); 719 defaultView = getErrorView(); 720 } 721 722 return defaultView; 723 } 724 725 /** 726 * Inflate and return a view that represents an error state. 727 */ getErrorView()728 protected View getErrorView() { 729 TextView tv = new TextView(mContext); 730 tv.setText(com.android.internal.R.string.gadget_host_error_inflating); 731 // TODO: get this color from somewhere. 732 tv.setBackgroundColor(Color.argb(127, 0, 0, 0)); 733 return tv; 734 } 735 736 /** @hide */ 737 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)738 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 739 super.onInitializeAccessibilityNodeInfoInternal(info); 740 info.setClassName(AppWidgetHostView.class.getName()); 741 } 742 743 private static class ParcelableSparseArray extends SparseArray<Parcelable> implements Parcelable { describeContents()744 public int describeContents() { 745 return 0; 746 } 747 writeToParcel(Parcel dest, int flags)748 public void writeToParcel(Parcel dest, int flags) { 749 final int count = size(); 750 dest.writeInt(count); 751 for (int i = 0; i < count; i++) { 752 dest.writeInt(keyAt(i)); 753 dest.writeParcelable(valueAt(i), 0); 754 } 755 } 756 757 public static final Parcelable.Creator<ParcelableSparseArray> CREATOR = 758 new Parcelable.Creator<ParcelableSparseArray>() { 759 public ParcelableSparseArray createFromParcel(Parcel source) { 760 final ParcelableSparseArray array = new ParcelableSparseArray(); 761 final ClassLoader loader = array.getClass().getClassLoader(); 762 final int count = source.readInt(); 763 for (int i = 0; i < count; i++) { 764 array.put(source.readInt(), source.readParcelable(loader)); 765 } 766 return array; 767 } 768 769 public ParcelableSparseArray[] newArray(int size) { 770 return new ParcelableSparseArray[size]; 771 } 772 }; 773 } 774 } 775