FileDocCategorySizeDatePackage
AppWidgetHostView.javaAPI DocAndroid 5.1 API24769Thu Mar 12 22:22:10 GMT 2015android.appwidget

AppWidgetHostView

public 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)

hide

        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)}.

param
animationIn Resource ID of in animation to use
param
animationOut Resource ID of out animation to use

        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 voiddispatchRestoreInstanceState(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 voiddispatchSaveInstanceState(android.util.SparseArray container)

        final ParcelableSparseArray jail = new ParcelableSparseArray();
        super.dispatchSaveInstanceState(jail);
        container.put(generateId(), jail);
    
protected booleandrawChild(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 intgenerateId()

        final int id = getId();
        return id == View.NO_ID ? mAppWidgetId : id;
    
public LayoutParamsgenerateLayoutParams(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 intgetAppWidgetId()

        return mAppWidgetId;
    
public AppWidgetProviderInfogetAppWidgetInfo()

        return mInfo;
    
public static android.graphics.RectgetDefaultPaddingForWidget(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.

param
context the current context
param
component the component name of the widget
param
padding Rect in which to place the output, if null, a new Rect will be allocated and returned
return
default padding for this widget, in pixels

        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.ViewgetDefaultView()
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.ViewgetErrorView()
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.ContextgetRemoteContext()
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 voidonInitializeAccessibilityNodeInfo(android.view.accessibility.AccessibilityNodeInfo info)

        super.onInitializeAccessibilityNodeInfo(info);
        info.setClassName(AppWidgetHostView.class.getName());
    
protected voidprepareView(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);
    
voidresetAppWidget(AppWidgetProviderInfo info)
Update the AppWidgetProviderInfo for this view, and reset it to the initial layout.

        mInfo = info;
        mViewMode = VIEW_MODE_NOINIT;
        updateAppWidget(null);
    
public voidsetAppWidget(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 voidsetOnClickHandler(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.

param
handler
hide

        mOnClickHandler = handler;
    
public voidupdateAppWidget(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 voidupdateAppWidgetOptions(android.os.Bundle options)
Specify some extra information for the widget provider. Causes a callback to the AppWidgetProvider.

see
AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
param
options The bundle of options information.

        AppWidgetManager.getInstance(mContext).updateAppWidgetOptions(mAppWidgetId, options);
    
public voidupdateAppWidgetSize(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.

see
AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
param
newOptions The bundle of options, in addition to the size information, can be null.
param
minWidth The minimum width in dips that the widget will be displayed at.
param
minHeight The maximum height in dips that the widget will be displayed at.
param
maxWidth The maximum width in dips that the widget will be displayed at.
param
maxHeight The maximum height in dips that the widget will be displayed at.

        updateAppWidgetSize(newOptions, minWidth, minHeight, maxWidth, maxHeight, false);
    
public voidupdateAppWidgetSize(android.os.Bundle newOptions, int minWidth, int minHeight, int maxWidth, int maxHeight, boolean ignorePadding)

hide

        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);
        }
    
voidviewDataChanged(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();
            }
        }