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 and applying the remoteviews. 351 * @see {@link RemoteViews#applyAsync(Context, ViewGroup, RemoteViews.OnViewAppliedListener, Executor)} 352 * 353 * @param executor the executor to use or null. 354 * @hide 355 */ setAsyncExecutor(Executor executor)356 public void setAsyncExecutor(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); 381 } 382 383 /** 384 * @hide 385 */ applyRemoteViews(RemoteViews remoteViews)386 protected void applyRemoteViews(RemoteViews remoteViews) { 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) { 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 = launcherApps.getApplicationInfo( 502 info.provider.getPackageName(), 0, info.getProfile()); 503 if (appInfo != null && 504 (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) { 505 setContentDescription( 506 Resources.getSystem().getString( 507 com.android.internal.R.string.suspended_widget_accessibility, info.label)); 508 } else { 509 setContentDescription(info.label); 510 } 511 } 512 } 513 inflateAsync(RemoteViews remoteViews)514 private void inflateAsync(RemoteViews remoteViews) { 515 // Prepare a local reference to the remote Context so we're ready to 516 // inflate any requested LayoutParams. 517 mRemoteContext = getRemoteContext(); 518 int layoutId = remoteViews.getLayoutId(); 519 520 // If our stale view has been prepared to match active, and the new 521 // layout matches, try recycling it 522 if (layoutId == mLayoutId && mView != null) { 523 try { 524 mLastExecutionSignal = remoteViews.reapplyAsync(mContext, 525 mView, 526 mAsyncExecutor, 527 new ViewApplyListener(remoteViews, layoutId, true), 528 mOnClickHandler); 529 } catch (Exception e) { 530 // Reapply failed. Try apply 531 } 532 } 533 if (mLastExecutionSignal == null) { 534 mLastExecutionSignal = remoteViews.applyAsync(mContext, 535 this, 536 mAsyncExecutor, 537 new ViewApplyListener(remoteViews, layoutId, false), 538 mOnClickHandler); 539 } 540 } 541 542 private class ViewApplyListener implements RemoteViews.OnViewAppliedListener { 543 private final RemoteViews mViews; 544 private final boolean mIsReapply; 545 private final int mLayoutId; 546 ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply)547 public ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply) { 548 mViews = views; 549 mLayoutId = layoutId; 550 mIsReapply = isReapply; 551 } 552 553 @Override onViewApplied(View v)554 public void onViewApplied(View v) { 555 AppWidgetHostView.this.mLayoutId = mLayoutId; 556 mViewMode = VIEW_MODE_CONTENT; 557 558 applyContent(v, mIsReapply, null); 559 } 560 561 @Override onError(Exception e)562 public void onError(Exception e) { 563 if (mIsReapply) { 564 // Try a fresh replay 565 mLastExecutionSignal = mViews.applyAsync(mContext, 566 AppWidgetHostView.this, 567 mAsyncExecutor, 568 new ViewApplyListener(mViews, mLayoutId, false), 569 mOnClickHandler); 570 } else { 571 applyContent(null, false, e); 572 } 573 } 574 } 575 576 /** 577 * Process data-changed notifications for the specified view in the specified 578 * set of {@link RemoteViews} views. 579 */ viewDataChanged(int viewId)580 void viewDataChanged(int viewId) { 581 View v = findViewById(viewId); 582 if ((v != null) && (v instanceof AdapterView<?>)) { 583 AdapterView<?> adapterView = (AdapterView<?>) v; 584 Adapter adapter = adapterView.getAdapter(); 585 if (adapter instanceof BaseAdapter) { 586 BaseAdapter baseAdapter = (BaseAdapter) adapter; 587 baseAdapter.notifyDataSetChanged(); 588 } else if (adapter == null && adapterView instanceof RemoteAdapterConnectionCallback) { 589 // If the adapter is null, it may mean that the RemoteViewsAapter has not yet 590 // connected to its associated service, and hence the adapter hasn't been set. 591 // In this case, we need to defer the notify call until it has been set. 592 ((RemoteAdapterConnectionCallback) adapterView).deferNotifyDataSetChanged(); 593 } 594 } 595 } 596 597 /** 598 * Build a {@link Context} cloned into another package name, usually for the 599 * purposes of reading remote resources. 600 * @hide 601 */ getRemoteContext()602 protected Context getRemoteContext() { 603 try { 604 // Return if cloned successfully, otherwise default 605 return mContext.createApplicationContext( 606 mInfo.providerInfo.applicationInfo, 607 Context.CONTEXT_RESTRICTED); 608 } catch (NameNotFoundException e) { 609 Log.e(TAG, "Package name " + mInfo.providerInfo.packageName + " not found"); 610 return mContext; 611 } 612 } 613 614 @Override drawChild(Canvas canvas, View child, long drawingTime)615 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 616 if (CROSSFADE) { 617 int alpha; 618 int l = child.getLeft(); 619 int t = child.getTop(); 620 if (mFadeStartTime > 0) { 621 alpha = (int)(((drawingTime-mFadeStartTime)*255)/FADE_DURATION); 622 if (alpha > 255) { 623 alpha = 255; 624 } 625 Log.d(TAG, "drawChild alpha=" + alpha + " l=" + l + " t=" + t 626 + " w=" + child.getWidth()); 627 if (alpha != 255 && mOld != null) { 628 mOldPaint.setAlpha(255-alpha); 629 //canvas.drawBitmap(mOld, l, t, mOldPaint); 630 } 631 } else { 632 alpha = 255; 633 } 634 int restoreTo = canvas.saveLayerAlpha(l, t, child.getWidth(), child.getHeight(), alpha, 635 Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); 636 boolean rv = super.drawChild(canvas, child, drawingTime); 637 canvas.restoreToCount(restoreTo); 638 if (alpha < 255) { 639 invalidate(); 640 } else { 641 mFadeStartTime = -1; 642 if (mOld != null) { 643 mOld.recycle(); 644 mOld = null; 645 } 646 } 647 return rv; 648 } else { 649 return super.drawChild(canvas, child, drawingTime); 650 } 651 } 652 653 /** 654 * Prepare the given view to be shown. This might include adjusting 655 * {@link FrameLayout.LayoutParams} before inserting. 656 */ prepareView(View view)657 protected void prepareView(View view) { 658 // Take requested dimensions from child, but apply default gravity. 659 FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams)view.getLayoutParams(); 660 if (requested == null) { 661 requested = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, 662 LayoutParams.MATCH_PARENT); 663 } 664 665 requested.gravity = Gravity.CENTER; 666 view.setLayoutParams(requested); 667 } 668 669 /** 670 * Inflate and return the default layout requested by AppWidget provider. 671 */ getDefaultView()672 protected View getDefaultView() { 673 if (LOGD) { 674 Log.d(TAG, "getDefaultView"); 675 } 676 View defaultView = null; 677 Exception exception = null; 678 679 try { 680 if (mInfo != null) { 681 Context theirContext = getRemoteContext(); 682 mRemoteContext = theirContext; 683 LayoutInflater inflater = (LayoutInflater) 684 theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 685 inflater = inflater.cloneInContext(theirContext); 686 inflater.setFilter(sInflaterFilter); 687 AppWidgetManager manager = AppWidgetManager.getInstance(mContext); 688 Bundle options = manager.getAppWidgetOptions(mAppWidgetId); 689 690 int layoutId = mInfo.initialLayout; 691 if (options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) { 692 int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY); 693 if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) { 694 int kgLayoutId = mInfo.initialKeyguardLayout; 695 // If a default keyguard layout is not specified, use the standard 696 // default layout. 697 layoutId = kgLayoutId == 0 ? layoutId : kgLayoutId; 698 } 699 } 700 defaultView = inflater.inflate(layoutId, this, false); 701 } else { 702 Log.w(TAG, "can't inflate defaultView because mInfo is missing"); 703 } 704 } catch (RuntimeException e) { 705 exception = e; 706 } 707 708 if (exception != null) { 709 Log.w(TAG, "Error inflating AppWidget " + mInfo + ": " + exception.toString()); 710 } 711 712 if (defaultView == null) { 713 if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error"); 714 defaultView = getErrorView(); 715 } 716 717 return defaultView; 718 } 719 720 /** 721 * Inflate and return a view that represents an error state. 722 */ getErrorView()723 protected View getErrorView() { 724 TextView tv = new TextView(mContext); 725 tv.setText(com.android.internal.R.string.gadget_host_error_inflating); 726 // TODO: get this color from somewhere. 727 tv.setBackgroundColor(Color.argb(127, 0, 0, 0)); 728 return tv; 729 } 730 731 /** @hide */ 732 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)733 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 734 super.onInitializeAccessibilityNodeInfoInternal(info); 735 info.setClassName(AppWidgetHostView.class.getName()); 736 } 737 738 private static class ParcelableSparseArray extends SparseArray<Parcelable> implements Parcelable { describeContents()739 public int describeContents() { 740 return 0; 741 } 742 writeToParcel(Parcel dest, int flags)743 public void writeToParcel(Parcel dest, int flags) { 744 final int count = size(); 745 dest.writeInt(count); 746 for (int i = 0; i < count; i++) { 747 dest.writeInt(keyAt(i)); 748 dest.writeParcelable(valueAt(i), 0); 749 } 750 } 751 752 public static final Parcelable.Creator<ParcelableSparseArray> CREATOR = 753 new Parcelable.Creator<ParcelableSparseArray>() { 754 public ParcelableSparseArray createFromParcel(Parcel source) { 755 final ParcelableSparseArray array = new ParcelableSparseArray(); 756 final ClassLoader loader = array.getClass().getClassLoader(); 757 final int count = source.readInt(); 758 for (int i = 0; i < count; i++) { 759 array.put(source.readInt(), source.readParcelable(loader)); 760 } 761 return array; 762 } 763 764 public ParcelableSparseArray[] newArray(int size) { 765 return new ParcelableSparseArray[size]; 766 } 767 }; 768 } 769 } 770