Home | History | Annotate | Download | only in appwidget
      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.PackageManager.NameNotFoundException;
     23 import android.content.res.Resources;
     24 import android.graphics.Color;
     25 import android.graphics.Rect;
     26 import android.os.Build;
     27 import android.os.Bundle;
     28 import android.os.CancellationSignal;
     29 import android.os.Parcelable;
     30 import android.util.AttributeSet;
     31 import android.util.Log;
     32 import android.util.SparseArray;
     33 import android.view.Gravity;
     34 import android.view.LayoutInflater;
     35 import android.view.View;
     36 import android.view.accessibility.AccessibilityNodeInfo;
     37 import android.widget.Adapter;
     38 import android.widget.AdapterView;
     39 import android.widget.BaseAdapter;
     40 import android.widget.FrameLayout;
     41 import android.widget.RemoteViews;
     42 import android.widget.RemoteViews.OnClickHandler;
     43 import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback;
     44 import android.widget.TextView;
     45 
     46 import java.util.concurrent.Executor;
     47 
     48 /**
     49  * Provides the glue to show AppWidget views. This class offers automatic animation
     50  * between updates, and will try recycling old views for each incoming
     51  * {@link RemoteViews}.
     52  */
     53 public class AppWidgetHostView extends FrameLayout {
     54 
     55     static final String TAG = "AppWidgetHostView";
     56     private static final String KEY_JAILED_ARRAY = "jail";
     57 
     58     static final boolean LOGD = false;
     59 
     60     static final int VIEW_MODE_NOINIT = 0;
     61     static final int VIEW_MODE_CONTENT = 1;
     62     static final int VIEW_MODE_ERROR = 2;
     63     static final int VIEW_MODE_DEFAULT = 3;
     64 
     65     // When we're inflating the initialLayout for a AppWidget, we only allow
     66     // views that are allowed in RemoteViews.
     67     private static final LayoutInflater.Filter INFLATER_FILTER =
     68             (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
     69 
     70     Context mContext;
     71     Context mRemoteContext;
     72 
     73     int mAppWidgetId;
     74     AppWidgetProviderInfo mInfo;
     75     View mView;
     76     int mViewMode = VIEW_MODE_NOINIT;
     77     int mLayoutId = -1;
     78     private OnClickHandler mOnClickHandler;
     79 
     80     private Executor mAsyncExecutor;
     81     private CancellationSignal mLastExecutionSignal;
     82 
     83     /**
     84      * Create a host view.  Uses default fade animations.
     85      */
     86     public AppWidgetHostView(Context context) {
     87         this(context, android.R.anim.fade_in, android.R.anim.fade_out);
     88     }
     89 
     90     /**
     91      * @hide
     92      */
     93     public AppWidgetHostView(Context context, OnClickHandler handler) {
     94         this(context, android.R.anim.fade_in, android.R.anim.fade_out);
     95         mOnClickHandler = handler;
     96     }
     97 
     98     /**
     99      * Create a host view. Uses specified animations when pushing
    100      * {@link #updateAppWidget(RemoteViews)}.
    101      *
    102      * @param animationIn Resource ID of in animation to use
    103      * @param animationOut Resource ID of out animation to use
    104      */
    105     @SuppressWarnings({"UnusedDeclaration"})
    106     public AppWidgetHostView(Context context, int animationIn, int animationOut) {
    107         super(context);
    108         mContext = context;
    109         // We want to segregate the view ids within AppWidgets to prevent
    110         // problems when those ids collide with view ids in the AppWidgetHost.
    111         setIsRootNamespace(true);
    112     }
    113 
    114     /**
    115      * Pass the given handler to RemoteViews when updating this widget. Unless this
    116      * is done immediatly after construction, a call to {@link #updateAppWidget(RemoteViews)}
    117      * should be made.
    118      * @param handler
    119      * @hide
    120      */
    121     public void setOnClickHandler(OnClickHandler handler) {
    122         mOnClickHandler = handler;
    123     }
    124 
    125     /**
    126      * Set the AppWidget that will be displayed by this view. This method also adds default padding
    127      * to widgets, as described in {@link #getDefaultPaddingForWidget(Context, ComponentName, Rect)}
    128      * and can be overridden in order to add custom padding.
    129      */
    130     public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) {
    131         mAppWidgetId = appWidgetId;
    132         mInfo = info;
    133 
    134         // We add padding to the AppWidgetHostView if necessary
    135         Rect padding = getDefaultPadding();
    136         setPadding(padding.left, padding.top, padding.right, padding.bottom);
    137 
    138         // Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for
    139         // a widget, eg. for some widgets in safe mode.
    140         if (info != null) {
    141             String description = info.loadLabel(getContext().getPackageManager());
    142             if ((info.providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) {
    143                 description = Resources.getSystem().getString(
    144                         com.android.internal.R.string.suspended_widget_accessibility, description);
    145             }
    146             setContentDescription(description);
    147         }
    148     }
    149 
    150     /**
    151      * As of ICE_CREAM_SANDWICH we are automatically adding padding to widgets targeting
    152      * ICE_CREAM_SANDWICH and higher. The new widget design guidelines strongly recommend
    153      * that widget developers do not add extra padding to their widgets. This will help
    154      * achieve consistency among widgets.
    155      *
    156      * Note: this method is only needed by developers of AppWidgetHosts. The method is provided in
    157      * order for the AppWidgetHost to account for the automatic padding when computing the number
    158      * of cells to allocate to a particular widget.
    159      *
    160      * @param context the current context
    161      * @param component the component name of the widget
    162      * @param padding Rect in which to place the output, if null, a new Rect will be allocated and
    163      *                returned
    164      * @return default padding for this widget, in pixels
    165      */
    166     public static Rect getDefaultPaddingForWidget(Context context, ComponentName component,
    167             Rect padding) {
    168         ApplicationInfo appInfo = null;
    169         try {
    170             appInfo = context.getPackageManager().getApplicationInfo(component.getPackageName(), 0);
    171         } catch (NameNotFoundException e) {
    172             // if we can't find the package, ignore
    173         }
    174         return getDefaultPaddingForWidget(context, appInfo, padding);
    175     }
    176 
    177     private static Rect getDefaultPaddingForWidget(Context context, ApplicationInfo appInfo,
    178             Rect padding) {
    179         if (padding == null) {
    180             padding = new Rect(0, 0, 0, 0);
    181         } else {
    182             padding.set(0, 0, 0, 0);
    183         }
    184         if (appInfo != null && appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
    185             Resources r = context.getResources();
    186             padding.left = r.getDimensionPixelSize(com.android.internal.
    187                     R.dimen.default_app_widget_padding_left);
    188             padding.right = r.getDimensionPixelSize(com.android.internal.
    189                     R.dimen.default_app_widget_padding_right);
    190             padding.top = r.getDimensionPixelSize(com.android.internal.
    191                     R.dimen.default_app_widget_padding_top);
    192             padding.bottom = r.getDimensionPixelSize(com.android.internal.
    193                     R.dimen.default_app_widget_padding_bottom);
    194         }
    195         return padding;
    196     }
    197 
    198     private Rect getDefaultPadding() {
    199         return getDefaultPaddingForWidget(mContext,
    200                 mInfo == null ? null : mInfo.providerInfo.applicationInfo, null);
    201     }
    202 
    203     public int getAppWidgetId() {
    204         return mAppWidgetId;
    205     }
    206 
    207     public AppWidgetProviderInfo getAppWidgetInfo() {
    208         return mInfo;
    209     }
    210 
    211     @Override
    212     protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
    213         final SparseArray<Parcelable> jail = new SparseArray<>();
    214         super.dispatchSaveInstanceState(jail);
    215 
    216         Bundle bundle = new Bundle();
    217         bundle.putSparseParcelableArray(KEY_JAILED_ARRAY, jail);
    218         container.put(generateId(), bundle);
    219     }
    220 
    221     private int generateId() {
    222         final int id = getId();
    223         return id == View.NO_ID ? mAppWidgetId : id;
    224     }
    225 
    226     @Override
    227     protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
    228         final Parcelable parcelable = container.get(generateId());
    229 
    230         SparseArray<Parcelable> jail = null;
    231         if (parcelable instanceof Bundle) {
    232             jail = ((Bundle) parcelable).getSparseParcelableArray(KEY_JAILED_ARRAY);
    233         }
    234 
    235         if (jail == null) jail = new SparseArray<>();
    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
    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      */
    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      */
    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 = getDefaultPadding();
    294         float density = getResources().getDisplayMetrics().density;
    295 
    296         int xPaddingDips = (int) ((padding.left + padding.right) / density);
    297         int yPaddingDips = (int) ((padding.top + padding.bottom) / density);
    298 
    299         int newMinWidth = minWidth - (ignorePadding ? 0 : xPaddingDips);
    300         int newMinHeight = minHeight - (ignorePadding ? 0 : yPaddingDips);
    301         int newMaxWidth = maxWidth - (ignorePadding ? 0 : xPaddingDips);
    302         int newMaxHeight = maxHeight - (ignorePadding ? 0 : yPaddingDips);
    303 
    304         AppWidgetManager widgetManager = AppWidgetManager.getInstance(mContext);
    305 
    306         // We get the old options to see if the sizes have changed
    307         Bundle oldOptions = widgetManager.getAppWidgetOptions(mAppWidgetId);
    308         boolean needsUpdate = false;
    309         if (newMinWidth != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) ||
    310                 newMinHeight != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) ||
    311                 newMaxWidth != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH) ||
    312                 newMaxHeight != oldOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)) {
    313             needsUpdate = true;
    314         }
    315 
    316         if (needsUpdate) {
    317             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, newMinWidth);
    318             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, newMinHeight);
    319             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, newMaxWidth);
    320             newOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, newMaxHeight);
    321             updateAppWidgetOptions(newOptions);
    322         }
    323     }
    324 
    325     /**
    326      * Specify some extra information for the widget provider. Causes a callback to the
    327      * AppWidgetProvider.
    328      * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
    329      *
    330      * @param options The bundle of options information.
    331      */
    332     public void updateAppWidgetOptions(Bundle options) {
    333         AppWidgetManager.getInstance(mContext).updateAppWidgetOptions(mAppWidgetId, options);
    334     }
    335 
    336     /** {@inheritDoc} */
    337     @Override
    338     public LayoutParams generateLayoutParams(AttributeSet attrs) {
    339         // We're being asked to inflate parameters, probably by a LayoutInflater
    340         // in a remote Context. To help resolve any remote references, we
    341         // inflate through our last mRemoteContext when it exists.
    342         final Context context = mRemoteContext != null ? mRemoteContext : mContext;
    343         return new FrameLayout.LayoutParams(context, attrs);
    344     }
    345 
    346     /**
    347      * Sets an executor which can be used for asynchronously inflating. CPU intensive tasks like
    348      * view inflation or loading images will be performed on the executor. The updates will still
    349      * be applied on the UI thread.
    350      *
    351      * @param executor the executor to use or null.
    352      */
    353     public void setExecutor(Executor executor) {
    354         if (mLastExecutionSignal != null) {
    355             mLastExecutionSignal.cancel();
    356             mLastExecutionSignal = null;
    357         }
    358 
    359         mAsyncExecutor = executor;
    360     }
    361 
    362     /**
    363      * Update the AppWidgetProviderInfo for this view, and reset it to the
    364      * initial layout.
    365      */
    366     void resetAppWidget(AppWidgetProviderInfo info) {
    367         setAppWidget(mAppWidgetId, info);
    368         mViewMode = VIEW_MODE_NOINIT;
    369         updateAppWidget(null);
    370     }
    371 
    372     /**
    373      * Process a set of {@link RemoteViews} coming in as an update from the
    374      * AppWidget provider. Will animate into these new views as needed
    375      */
    376     public void updateAppWidget(RemoteViews remoteViews) {
    377         applyRemoteViews(remoteViews, true);
    378     }
    379 
    380     /**
    381      * @hide
    382      */
    383     protected void applyRemoteViews(RemoteViews remoteViews, boolean useAsyncIfPossible) {
    384         boolean recycled = false;
    385         View content = null;
    386         Exception exception = null;
    387 
    388         if (mLastExecutionSignal != null) {
    389             mLastExecutionSignal.cancel();
    390             mLastExecutionSignal = null;
    391         }
    392 
    393         if (remoteViews == null) {
    394             if (mViewMode == VIEW_MODE_DEFAULT) {
    395                 // We've already done this -- nothing to do.
    396                 return;
    397             }
    398             content = getDefaultView();
    399             mLayoutId = -1;
    400             mViewMode = VIEW_MODE_DEFAULT;
    401         } else {
    402             if (mAsyncExecutor != null && useAsyncIfPossible) {
    403                 inflateAsync(remoteViews);
    404                 return;
    405             }
    406             // Prepare a local reference to the remote Context so we're ready to
    407             // inflate any requested LayoutParams.
    408             mRemoteContext = getRemoteContext();
    409             int layoutId = remoteViews.getLayoutId();
    410 
    411             // If our stale view has been prepared to match active, and the new
    412             // layout matches, try recycling it
    413             if (content == null && layoutId == mLayoutId) {
    414                 try {
    415                     remoteViews.reapply(mContext, mView, mOnClickHandler);
    416                     content = mView;
    417                     recycled = true;
    418                     if (LOGD) Log.d(TAG, "was able to recycle existing layout");
    419                 } catch (RuntimeException e) {
    420                     exception = e;
    421                 }
    422             }
    423 
    424             // Try normal RemoteView inflation
    425             if (content == null) {
    426                 try {
    427                     content = remoteViews.apply(mContext, this, mOnClickHandler);
    428                     if (LOGD) Log.d(TAG, "had to inflate new layout");
    429                 } catch (RuntimeException e) {
    430                     exception = e;
    431                 }
    432             }
    433 
    434             mLayoutId = layoutId;
    435             mViewMode = VIEW_MODE_CONTENT;
    436         }
    437 
    438         applyContent(content, recycled, exception);
    439     }
    440 
    441     private void applyContent(View content, boolean recycled, Exception exception) {
    442         if (content == null) {
    443             if (mViewMode == VIEW_MODE_ERROR) {
    444                 // We've already done this -- nothing to do.
    445                 return ;
    446             }
    447             if (exception != null) {
    448                 Log.w(TAG, "Error inflating RemoteViews : " + exception.toString());
    449             }
    450             content = getErrorView();
    451             mViewMode = VIEW_MODE_ERROR;
    452         }
    453 
    454         if (!recycled) {
    455             prepareView(content);
    456             addView(content);
    457         }
    458 
    459         if (mView != content) {
    460             removeView(mView);
    461             mView = content;
    462         }
    463     }
    464 
    465     private void inflateAsync(RemoteViews remoteViews) {
    466         // Prepare a local reference to the remote Context so we're ready to
    467         // inflate any requested LayoutParams.
    468         mRemoteContext = getRemoteContext();
    469         int layoutId = remoteViews.getLayoutId();
    470 
    471         // If our stale view has been prepared to match active, and the new
    472         // layout matches, try recycling it
    473         if (layoutId == mLayoutId && mView != null) {
    474             try {
    475                 mLastExecutionSignal = remoteViews.reapplyAsync(mContext,
    476                         mView,
    477                         mAsyncExecutor,
    478                         new ViewApplyListener(remoteViews, layoutId, true),
    479                         mOnClickHandler);
    480             } catch (Exception e) {
    481                 // Reapply failed. Try apply
    482             }
    483         }
    484         if (mLastExecutionSignal == null) {
    485             mLastExecutionSignal = remoteViews.applyAsync(mContext,
    486                     this,
    487                     mAsyncExecutor,
    488                     new ViewApplyListener(remoteViews, layoutId, false),
    489                     mOnClickHandler);
    490         }
    491     }
    492 
    493     private class ViewApplyListener implements RemoteViews.OnViewAppliedListener {
    494         private final RemoteViews mViews;
    495         private final boolean mIsReapply;
    496         private final int mLayoutId;
    497 
    498         public ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply) {
    499             mViews = views;
    500             mLayoutId = layoutId;
    501             mIsReapply = isReapply;
    502         }
    503 
    504         @Override
    505         public void onViewApplied(View v) {
    506             AppWidgetHostView.this.mLayoutId = mLayoutId;
    507             mViewMode = VIEW_MODE_CONTENT;
    508 
    509             applyContent(v, mIsReapply, null);
    510         }
    511 
    512         @Override
    513         public void onError(Exception e) {
    514             if (mIsReapply) {
    515                 // Try a fresh replay
    516                 mLastExecutionSignal = mViews.applyAsync(mContext,
    517                         AppWidgetHostView.this,
    518                         mAsyncExecutor,
    519                         new ViewApplyListener(mViews, mLayoutId, false),
    520                         mOnClickHandler);
    521             } else {
    522                 applyContent(null, false, e);
    523             }
    524         }
    525     }
    526 
    527     /**
    528      * Process data-changed notifications for the specified view in the specified
    529      * set of {@link RemoteViews} views.
    530      */
    531     void viewDataChanged(int viewId) {
    532         View v = findViewById(viewId);
    533         if ((v != null) && (v instanceof AdapterView<?>)) {
    534             AdapterView<?> adapterView = (AdapterView<?>) v;
    535             Adapter adapter = adapterView.getAdapter();
    536             if (adapter instanceof BaseAdapter) {
    537                 BaseAdapter baseAdapter = (BaseAdapter) adapter;
    538                 baseAdapter.notifyDataSetChanged();
    539             }  else if (adapter == null && adapterView instanceof RemoteAdapterConnectionCallback) {
    540                 // If the adapter is null, it may mean that the RemoteViewsAapter has not yet
    541                 // connected to its associated service, and hence the adapter hasn't been set.
    542                 // In this case, we need to defer the notify call until it has been set.
    543                 ((RemoteAdapterConnectionCallback) adapterView).deferNotifyDataSetChanged();
    544             }
    545         }
    546     }
    547 
    548     /**
    549      * Build a {@link Context} cloned into another package name, usually for the
    550      * purposes of reading remote resources.
    551      * @hide
    552      */
    553     protected Context getRemoteContext() {
    554         try {
    555             // Return if cloned successfully, otherwise default
    556             return mContext.createApplicationContext(
    557                     mInfo.providerInfo.applicationInfo,
    558                     Context.CONTEXT_RESTRICTED);
    559         } catch (NameNotFoundException e) {
    560             Log.e(TAG, "Package name " +  mInfo.providerInfo.packageName + " not found");
    561             return mContext;
    562         }
    563     }
    564 
    565     /**
    566      * Prepare the given view to be shown. This might include adjusting
    567      * {@link FrameLayout.LayoutParams} before inserting.
    568      */
    569     protected void prepareView(View view) {
    570         // Take requested dimensions from child, but apply default gravity.
    571         FrameLayout.LayoutParams requested = (FrameLayout.LayoutParams)view.getLayoutParams();
    572         if (requested == null) {
    573             requested = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
    574                     LayoutParams.MATCH_PARENT);
    575         }
    576 
    577         requested.gravity = Gravity.CENTER;
    578         view.setLayoutParams(requested);
    579     }
    580 
    581     /**
    582      * Inflate and return the default layout requested by AppWidget provider.
    583      */
    584     protected View getDefaultView() {
    585         if (LOGD) {
    586             Log.d(TAG, "getDefaultView");
    587         }
    588         View defaultView = null;
    589         Exception exception = null;
    590 
    591         try {
    592             if (mInfo != null) {
    593                 Context theirContext = getRemoteContext();
    594                 mRemoteContext = theirContext;
    595                 LayoutInflater inflater = (LayoutInflater)
    596                         theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    597                 inflater = inflater.cloneInContext(theirContext);
    598                 inflater.setFilter(INFLATER_FILTER);
    599                 AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
    600                 Bundle options = manager.getAppWidgetOptions(mAppWidgetId);
    601 
    602                 int layoutId = mInfo.initialLayout;
    603                 if (options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
    604                     int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY);
    605                     if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) {
    606                         int kgLayoutId = mInfo.initialKeyguardLayout;
    607                         // If a default keyguard layout is not specified, use the standard
    608                         // default layout.
    609                         layoutId = kgLayoutId == 0 ? layoutId : kgLayoutId;
    610                     }
    611                 }
    612                 defaultView = inflater.inflate(layoutId, this, false);
    613             } else {
    614                 Log.w(TAG, "can't inflate defaultView because mInfo is missing");
    615             }
    616         } catch (RuntimeException e) {
    617             exception = e;
    618         }
    619 
    620         if (exception != null) {
    621             Log.w(TAG, "Error inflating AppWidget " + mInfo + ": " + exception.toString());
    622         }
    623 
    624         if (defaultView == null) {
    625             if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error");
    626             defaultView = getErrorView();
    627         }
    628 
    629         return defaultView;
    630     }
    631 
    632     /**
    633      * Inflate and return a view that represents an error state.
    634      */
    635     protected View getErrorView() {
    636         TextView tv = new TextView(mContext);
    637         tv.setText(com.android.internal.R.string.gadget_host_error_inflating);
    638         // TODO: get this color from somewhere.
    639         tv.setBackgroundColor(Color.argb(127, 0, 0, 0));
    640         return tv;
    641     }
    642 
    643     /** @hide */
    644     @Override
    645     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
    646         super.onInitializeAccessibilityNodeInfoInternal(info);
    647         info.setClassName(AppWidgetHostView.class.getName());
    648     }
    649 }
    650