AppWidgetHostViewpublic class AppWidgetHostView extends android.widget.FrameLayout Provides the glue to show AppWidget views. This class offers automatic animation
between updates, and will try recycling old views for each incoming
{@link RemoteViews}. |
Fields Summary |
---|
static final String | TAG | static final boolean | LOGD | static final boolean | CROSSFADE | static final int | VIEW_MODE_NOINIT | static final int | VIEW_MODE_CONTENT | static final int | VIEW_MODE_ERROR | static final int | VIEW_MODE_DEFAULT | static final int | FADE_DURATION | static final LayoutInflater.Filter | sInflaterFilter | android.content.Context | mContext | android.content.Context | mRemoteContext | int | mAppWidgetId | AppWidgetProviderInfo | mInfo | android.view.View | mView | int | mViewMode | int | mLayoutId | long | mFadeStartTime | android.graphics.Bitmap | mOld | android.graphics.Paint | mOldPaint | private android.widget.RemoteViews.OnClickHandler | mOnClickHandler |
Constructors Summary |
---|
public AppWidgetHostView(android.content.Context context)Create a host view. Uses default fade animations.
this(context, android.R.anim.fade_in, android.R.anim.fade_out);
| public AppWidgetHostView(android.content.Context context, android.widget.RemoteViews.OnClickHandler handler)
this(context, android.R.anim.fade_in, android.R.anim.fade_out);
mOnClickHandler = handler;
| public AppWidgetHostView(android.content.Context context, int animationIn, int animationOut)Create a host view. Uses specified animations when pushing
{@link #updateAppWidget(RemoteViews)}.
super(context);
mContext = context;
// We want to segregate the view ids within AppWidgets to prevent
// problems when those ids collide with view ids in the AppWidgetHost.
setIsRootNamespace(true);
|
Methods Summary |
---|
protected void | dispatchRestoreInstanceState(android.util.SparseArray container)
final Parcelable parcelable = container.get(generateId());
ParcelableSparseArray jail = null;
if (parcelable != null && parcelable instanceof ParcelableSparseArray) {
jail = (ParcelableSparseArray) parcelable;
}
if (jail == null) jail = new ParcelableSparseArray();
try {
super.dispatchRestoreInstanceState(jail);
} catch (Exception e) {
Log.e(TAG, "failed to restoreInstanceState for widget id: " + mAppWidgetId + ", "
+ (mInfo == null ? "null" : mInfo.provider), e);
}
| protected void | dispatchSaveInstanceState(android.util.SparseArray container)
final ParcelableSparseArray jail = new ParcelableSparseArray();
super.dispatchSaveInstanceState(jail);
container.put(generateId(), jail);
| protected boolean | drawChild(android.graphics.Canvas canvas, android.view.View child, long drawingTime)
if (CROSSFADE) {
int alpha;
int l = child.getLeft();
int t = child.getTop();
if (mFadeStartTime > 0) {
alpha = (int)(((drawingTime-mFadeStartTime)*255)/FADE_DURATION);
if (alpha > 255) {
alpha = 255;
}
Log.d(TAG, "drawChild alpha=" + alpha + " l=" + l + " t=" + t
+ " w=" + child.getWidth());
if (alpha != 255 && mOld != null) {
mOldPaint.setAlpha(255-alpha);
//canvas.drawBitmap(mOld, l, t, mOldPaint);
}
} else {
alpha = 255;
}
int restoreTo = canvas.saveLayerAlpha(l, t, child.getWidth(), child.getHeight(), alpha,
Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
boolean rv = super.drawChild(canvas, child, drawingTime);
canvas.restoreToCount(restoreTo);
if (alpha < 255) {
invalidate();
} else {
mFadeStartTime = -1;
if (mOld != null) {
mOld.recycle();
mOld = null;
}
}
return rv;
} else {
return super.drawChild(canvas, child, drawingTime);
}
| private int | generateId()
final int id = getId();
return id == View.NO_ID ? mAppWidgetId : id;
| public LayoutParams | generateLayoutParams(android.util.AttributeSet attrs){@inheritDoc}
// We're being asked to inflate parameters, probably by a LayoutInflater
// in a remote Context. To help resolve any remote references, we
// inflate through our last mRemoteContext when it exists.
final Context context = mRemoteContext != null ? mRemoteContext : mContext;
return new FrameLayout.LayoutParams(context, attrs);
| public int | getAppWidgetId()
return mAppWidgetId;
| public AppWidgetProviderInfo | getAppWidgetInfo()
return mInfo;
| public static android.graphics.Rect | getDefaultPaddingForWidget(android.content.Context context, android.content.ComponentName component, android.graphics.Rect padding)As of ICE_CREAM_SANDWICH we are automatically adding padding to widgets targeting
ICE_CREAM_SANDWICH and higher. The new widget design guidelines strongly recommend
that widget developers do not add extra padding to their widgets. This will help
achieve consistency among widgets.
Note: this method is only needed by developers of AppWidgetHosts. The method is provided in
order for the AppWidgetHost to account for the automatic padding when computing the number
of cells to allocate to a particular widget.
PackageManager packageManager = context.getPackageManager();
ApplicationInfo appInfo;
if (padding == null) {
padding = new Rect(0, 0, 0, 0);
} else {
padding.set(0, 0, 0, 0);
}
try {
appInfo = packageManager.getApplicationInfo(component.getPackageName(), 0);
} catch (NameNotFoundException e) {
// if we can't find the package, return 0 padding
return padding;
}
if (appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
Resources r = context.getResources();
padding.left = r.getDimensionPixelSize(com.android.internal.
R.dimen.default_app_widget_padding_left);
padding.right = r.getDimensionPixelSize(com.android.internal.
R.dimen.default_app_widget_padding_right);
padding.top = r.getDimensionPixelSize(com.android.internal.
R.dimen.default_app_widget_padding_top);
padding.bottom = r.getDimensionPixelSize(com.android.internal.
R.dimen.default_app_widget_padding_bottom);
}
return padding;
| protected android.view.View | getDefaultView()Inflate and return the default layout requested by AppWidget provider.
if (LOGD) {
Log.d(TAG, "getDefaultView");
}
View defaultView = null;
Exception exception = null;
try {
if (mInfo != null) {
Context theirContext = getRemoteContext();
mRemoteContext = theirContext;
LayoutInflater inflater = (LayoutInflater)
theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater = inflater.cloneInContext(theirContext);
inflater.setFilter(sInflaterFilter);
AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
Bundle options = manager.getAppWidgetOptions(mAppWidgetId);
int layoutId = mInfo.initialLayout;
if (options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY);
if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) {
int kgLayoutId = mInfo.initialKeyguardLayout;
// If a default keyguard layout is not specified, use the standard
// default layout.
layoutId = kgLayoutId == 0 ? layoutId : kgLayoutId;
}
}
defaultView = inflater.inflate(layoutId, this, false);
} else {
Log.w(TAG, "can't inflate defaultView because mInfo is missing");
}
} catch (RuntimeException e) {
exception = e;
}
if (exception != null) {
Log.w(TAG, "Error inflating AppWidget " + mInfo + ": " + exception.toString());
}
if (defaultView == null) {
if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error");
defaultView = getErrorView();
}
return defaultView;
| protected android.view.View | getErrorView()Inflate and return a view that represents an error state.
TextView tv = new TextView(mContext);
tv.setText(com.android.internal.R.string.gadget_host_error_inflating);
// TODO: get this color from somewhere.
tv.setBackgroundColor(Color.argb(127, 0, 0, 0));
return tv;
| private android.content.Context | getRemoteContext()Build a {@link Context} cloned into another package name, usually for the
purposes of reading remote resources.
try {
// Return if cloned successfully, otherwise default
return mContext.createApplicationContext(
mInfo.providerInfo.applicationInfo,
Context.CONTEXT_RESTRICTED);
} catch (NameNotFoundException e) {
Log.e(TAG, "Package name " + mInfo.providerInfo.packageName + " not found");
return mContext;
}
| public void | onInitializeAccessibilityNodeInfo(android.view.accessibility.AccessibilityNodeInfo info)
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(AppWidgetHostView.class.getName());
| protected void | prepareView(android.view.View view)Prepare the given view to be shown. This might include adjusting
{@link FrameLayout.LayoutParams} before inserting.
// Take requested dimensions from child, but apply default gravity.
FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams)view.getLayoutParams();
if (requested == null) {
requested = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
}
requested.gravity = Gravity.CENTER;
view.setLayoutParams(requested);
| void | resetAppWidget(AppWidgetProviderInfo info)Update the AppWidgetProviderInfo for this view, and reset it to the
initial layout.
mInfo = info;
mViewMode = VIEW_MODE_NOINIT;
updateAppWidget(null);
| public void | setAppWidget(int appWidgetId, AppWidgetProviderInfo info)Set the AppWidget that will be displayed by this view. This method also adds default padding
to widgets, as described in {@link #getDefaultPaddingForWidget(Context, ComponentName, Rect)}
and can be overridden in order to add custom padding.
mAppWidgetId = appWidgetId;
mInfo = info;
// Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for
// a widget, eg. for some widgets in safe mode.
if (info != null) {
// We add padding to the AppWidgetHostView if necessary
Rect padding = getDefaultPaddingForWidget(mContext, info.provider, null);
setPadding(padding.left, padding.top, padding.right, padding.bottom);
setContentDescription(info.label);
}
| public void | setOnClickHandler(android.widget.RemoteViews.OnClickHandler handler)Pass the given handler to RemoteViews when updating this widget. Unless this
is done immediatly after construction, a call to {@link #updateAppWidget(RemoteViews)}
should be made.
mOnClickHandler = handler;
| public void | updateAppWidget(android.widget.RemoteViews remoteViews)Process a set of {@link RemoteViews} coming in as an update from the
AppWidget provider. Will animate into these new views as needed
if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld);
boolean recycled = false;
View content = null;
Exception exception = null;
// Capture the old view into a bitmap so we can do the crossfade.
if (CROSSFADE) {
if (mFadeStartTime < 0) {
if (mView != null) {
final int width = mView.getWidth();
final int height = mView.getHeight();
try {
mOld = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
} catch (OutOfMemoryError e) {
// we just won't do the fade
mOld = null;
}
if (mOld != null) {
//mView.drawIntoBitmap(mOld);
}
}
}
}
if (remoteViews == null) {
if (mViewMode == VIEW_MODE_DEFAULT) {
// We've already done this -- nothing to do.
return;
}
content = getDefaultView();
mLayoutId = -1;
mViewMode = VIEW_MODE_DEFAULT;
} else {
// Prepare a local reference to the remote Context so we're ready to
// inflate any requested LayoutParams.
mRemoteContext = getRemoteContext();
int layoutId = remoteViews.getLayoutId();
// If our stale view has been prepared to match active, and the new
// layout matches, try recycling it
if (content == null && layoutId == mLayoutId) {
try {
remoteViews.reapply(mContext, mView, mOnClickHandler);
content = mView;
recycled = true;
if (LOGD) Log.d(TAG, "was able to recycled existing layout");
} catch (RuntimeException e) {
exception = e;
}
}
// Try normal RemoteView inflation
if (content == null) {
try {
content = remoteViews.apply(mContext, this, mOnClickHandler);
if (LOGD) Log.d(TAG, "had to inflate new layout");
} catch (RuntimeException e) {
exception = e;
}
}
mLayoutId = layoutId;
mViewMode = VIEW_MODE_CONTENT;
}
if (content == null) {
if (mViewMode == VIEW_MODE_ERROR) {
// We've already done this -- nothing to do.
return ;
}
Log.w(TAG, "updateAppWidget couldn't find any view, using error view", exception);
content = getErrorView();
mViewMode = VIEW_MODE_ERROR;
}
if (!recycled) {
prepareView(content);
addView(content);
}
if (mView != content) {
removeView(mView);
mView = content;
}
if (CROSSFADE) {
if (mFadeStartTime < 0) {
// if there is already an animation in progress, don't do anything --
// the new view will pop in on top of the old one during the cross fade,
// and that looks okay.
mFadeStartTime = SystemClock.uptimeMillis();
invalidate();
}
}
| public void | updateAppWidgetOptions(android.os.Bundle options)Specify some extra information for the widget provider. Causes a callback to the
AppWidgetProvider.
AppWidgetManager.getInstance(mContext).updateAppWidgetOptions(mAppWidgetId, options);
| public void | updateAppWidgetSize(android.os.Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight)Provide guidance about the size of this widget to the AppWidgetManager. The widths and
heights should correspond to the full area the AppWidgetHostView is given. Padding added by
the framework will be accounted for automatically. This information gets embedded into the
AppWidget options and causes a callback to the AppWidgetProvider.
updateAppWidgetSize(newOptions, minWidth, minHeight, maxWidth, maxHeight, false);
| public void | updateAppWidgetSize(android.os.Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight, boolean ignorePadding)
if (newOptions == null) {
newOptions = new Bundle();
}
Rect padding = new Rect();
if (mInfo != null) {
padding = getDefaultPaddingForWidget(mContext, mInfo.provider, padding);
}
float density = getResources().getDisplayMetrics().density;
int xPaddingDips = (int) ((padding.left + padding.right) / density);
int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
int newMinWidth = minWidth - (ignorePadding ? 0 : xPaddingDips);
int newMinHeight = minHeight - (ignorePadding ? 0 : yPaddingDips);
int newMaxWidth = maxWidth - (ignorePadding ? 0 : xPaddingDips);
int newMaxHeight = maxHeight - (ignorePadding ? 0 : yPaddingDips);
AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext);
// We get the old options to see if the sizes have changed
Bundle oldOptions = widgetManager.getAppWidgetOptions(mAppWidgetId);
boolean needsUpdate = false;
if (newMinWidth != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) ||
newMinHeight != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) ||
newMaxWidth != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH) ||
newMaxHeight != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)) {
needsUpdate = true;
}
if (needsUpdate) {
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, newMinWidth);
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, newMinHeight);
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, newMaxWidth);
newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, newMaxHeight);
updateAppWidgetOptions(newOptions);
}
| void | viewDataChanged(int viewId)Process data-changed notifications for the specified view in the specified
set of {@link RemoteViews} views.
View v = findViewById(viewId);
if ((v != null) && (v instanceof AdapterView<?>)) {
AdapterView<?> adapterView = (AdapterView<?>) v;
Adapter adapter = adapterView.getAdapter();
if (adapter instanceof BaseAdapter) {
BaseAdapter baseAdapter = (BaseAdapter) adapter;
baseAdapter.notifyDataSetChanged();
} else if (adapter == null && adapterView instanceof RemoteAdapterConnectionCallback) {
// If the adapter is null, it may mean that the RemoteViewsAapter has not yet
// connected to its associated service, and hence the adapter hasn't been set.
// In this case, we need to defer the notify call until it has been set.
((RemoteAdapterConnectionCallback) adapterView).deferNotifyDataSetChanged();
}
}
|
|