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