• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
AppWidgetHostView(Context context)86     public AppWidgetHostView(Context context) {
87         this(context, android.R.anim.fade_in, android.R.anim.fade_out);
88     }
89 
90     /**
91      * @hide
92      */
AppWidgetHostView(Context context, OnClickHandler handler)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"})
AppWidgetHostView(Context context, int animationIn, int animationOut)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      */
setOnClickHandler(OnClickHandler handler)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      */
setAppWidget(int appWidgetId, AppWidgetProviderInfo info)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      */
getDefaultPaddingForWidget(Context context, ComponentName component, Rect padding)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 
getDefaultPaddingForWidget(Context context, ApplicationInfo appInfo, Rect padding)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 
getDefaultPadding()198     private Rect getDefaultPadding() {
199         return getDefaultPaddingForWidget(mContext,
200                 mInfo == null ? null : mInfo.providerInfo.applicationInfo, null);
201     }
202 
getAppWidgetId()203     public int getAppWidgetId() {
204         return mAppWidgetId;
205     }
206 
getAppWidgetInfo()207     public AppWidgetProviderInfo getAppWidgetInfo() {
208         return mInfo;
209     }
210 
211     @Override
dispatchSaveInstanceState(SparseArray<Parcelable> container)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 
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         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
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 = 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      */
updateAppWidgetOptions(Bundle options)332     public void updateAppWidgetOptions(Bundle options) {
333         AppWidgetManager.getInstance(mContext).updateAppWidgetOptions(mAppWidgetId, options);
334     }
335 
336     /** {@inheritDoc} */
337     @Override
generateLayoutParams(AttributeSet attrs)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      */
setExecutor(Executor executor)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      */
resetAppWidget(AppWidgetProviderInfo info)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      */
updateAppWidget(RemoteViews remoteViews)376     public void updateAppWidget(RemoteViews remoteViews) {
377         applyRemoteViews(remoteViews, true);
378     }
379 
380     /**
381      * @hide
382      */
applyRemoteViews(RemoteViews remoteViews, boolean useAsyncIfPossible)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 
applyContent(View content, boolean recycled, Exception exception)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 
inflateAsync(RemoteViews remoteViews)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 
ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply)498         public ViewApplyListener(RemoteViews views, int layoutId, boolean isReapply) {
499             mViews = views;
500             mLayoutId = layoutId;
501             mIsReapply = isReapply;
502         }
503 
504         @Override
onViewApplied(View v)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
onError(Exception e)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      */
viewDataChanged(int viewId)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      */
getRemoteContext()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      */
prepareView(View view)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      */
getDefaultView()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      */
getErrorView()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
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)645     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
646         super.onInitializeAccessibilityNodeInfoInternal(info);
647         info.setClassName(AppWidgetHostView.class.getName());
648     }
649 }
650